{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# pass2path\n",
    "## A variant of seq2seq Encoder-Decoder RNN model that learns pairs of (password, transition path), where given a password and a transition path, a new password is generated."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "# imports\n",
    "import numpy as np\n",
    "import tensorflow as tf\n",
    "import os\n",
    "import pickle\n",
    "import copy\n",
    "import string\n",
    "import json\n",
    "import csv\n",
    "import time\n",
    "from word2keypress import Keyboard\n",
    "from ast import literal_eval\n",
    "from tensorflow.contrib.seq2seq.python.ops import beam_search_ops\n",
    "from pathlib import Path"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "'''\n",
    "Preprocessing:\n",
    "We need to create look-up tables in order to translate characters to mathematical representation.\n",
    "'''\n",
    "\n",
    "# CODES = {'<PAD>': 0, '<EOS>': 1, '<UNK>': 2, '<GO>': 3 }\n",
    "# chars = list(string.ascii_letters) + list(string.punctuation) + list(string.digits) + [\" \", \"\\t\", \"\\x03\", \"\\x04\"]\n",
    "\n",
    "def create_lookup_tables_from_lst(char_lst):\n",
    "    '''\n",
    "    This function creates a dictionary out of a list with the added codes representing padding,\n",
    "    unknows components, start and end of a sequence\n",
    "    '''\n",
    "    CODES = {'<PAD>': 0, '<EOS>': 1, '<UNK>': 2, '<GO>': 3 }\n",
    "    # chars = list(string.ascii_letters) + list(string.punctuation) + list(string.digits) + [\" \", \"\\t\", \"\\x03\", \"\\x04\"]\n",
    "    # make a list of unique chars\n",
    "    vocab = set(char_lst)\n",
    "\n",
    "    # (1)\n",
    "    # starts with the special tokens\n",
    "    vocab_to_int = copy.copy(CODES)\n",
    "\n",
    "    # the index (v_i) will starts from 4 (the 2nd arg in enumerate() specifies the starting index)\n",
    "    # since vocab_to_int already contains special tokens\n",
    "    for v_i, v in enumerate(vocab, len(CODES)):\n",
    "        vocab_to_int[str(v)] = v_i # opposite would be int()\n",
    "    int_to_vocab = {v_i: v for v, v_i in vocab_to_int.items()}\n",
    "\n",
    "    return vocab_to_int, int_to_vocab\n",
    "\n",
    "def build_path_vocab(origin_vocab):\n",
    "    '''\n",
    "    This functions combines the path dictionary with the added codes.\n",
    "    '''\n",
    "    int_lst = origin_vocab.values()\n",
    "    path_to_int_vocab, int_to_path_vocab = create_lookup_tables_from_lst(int_lst)\n",
    "    return path_to_int_vocab, int_to_path_vocab"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Globals\n",
    "kb = Keyboard()\n",
    "# thisfolder = Path(__file__).absolute().parent\n",
    "# TRANS_to_IDX = json.load((thisfolder / 'data/trans_dict_2idx.json').open()) # py\n",
    "with open(('./data/trans_dict_2idx.json')) as f:\n",
    "    TRANS_to_IDX = json.load(f) # jupyter\n",
    "IDX_to_TRANS = {v: literal_eval(k) for k, v in TRANS_to_IDX.items()}\n",
    "char_lst = list(string.ascii_letters) + list(string.digits) + list(string.punctuation) + [\" \", \"\\t\", \"\\x03\", \"\\x04\"]\n",
    "source_vocab_to_int, source_int_to_vocab = create_lookup_tables_from_lst(char_lst)\n",
    "target_vocab_to_int, target_int_to_vocab = build_path_vocab(TRANS_to_IDX)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Password & Paths to Ids\n",
    "\n",
    "Two `(1)` lookup tables will be provided in `input_to_ids` functions as arguments. They will be used in the converting process for Passwords(source) and Transition Path(target) respectively.\n",
    "\n",
    "- original(raw) source & target datas contain a list of characters & integers respectively\n",
    "  - they are represented as a string \n",
    "\n",
    "- by accessing each password, need to convert character into the corresponding index.\n",
    "  - each character should be stored in a list\n",
    "  - this makes the resuling list as a 2-D array ( row: password, column: char index )\n",
    "  \n",
    "- for every target path, special token, `<EOS>` should be inserted at the end\n",
    "  - this token suggests when to stop creating a sequence"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "def input_to_ids(source_pws_lst, target_paths_lst, source_vocab_to_int, target_vocab_to_int):\n",
    "    \"\"\"\n",
    "    1st, 2nd args: lists passwords and paths to be converted\n",
    "    3rd, 4th args: lookup tables for 1st and 2nd args respectively   \n",
    "    return: A tuple of lists (source_id_pass, target_id_path) converted\n",
    "    Will use for mini-batches of (passwords, paths)\n",
    "    \"\"\"\n",
    "    # empty list of converted passwords and paths\n",
    "    source_pass_id = []\n",
    "    target_path_id = []\n",
    "    \n",
    "    max_source_pass_length = max([len(password) for password in source_pws_lst])\n",
    "    max_target_path_length = max([len(path) for path in target_paths_lst])\n",
    "    \n",
    "    # iterating through each password (# of passwords & paths is the same)\n",
    "    for i in range(len(source_pws_lst)):\n",
    "        # extract password, one by one\n",
    "        source_password = source_pws_lst[i]\n",
    "#         print(target_paths_lst)\n",
    "        target_path = target_paths_lst[i]\n",
    "        \n",
    "        # make a list of tokens/words (extraction) from the chosen password\n",
    "        source_tokens = list(source_password)\n",
    "        target_tokens = [str(t) for t in target_path]\n",
    "        \n",
    "        # empty list of converted words to index in the chosen password\n",
    "        source_token_id = []\n",
    "        target_token_id = []\n",
    "        \n",
    "        for index, token in enumerate(source_tokens):\n",
    "            if (token != \"\"):\n",
    "                if (source_vocab_to_int.get(token)):\n",
    "                    source_token_id.append(source_vocab_to_int[token])\n",
    "        \n",
    "        for index, token in enumerate(target_tokens):\n",
    "            if (token != \"\"):\n",
    "                if (target_vocab_to_int.get(token)):\n",
    "                    target_token_id.append(target_vocab_to_int[token])\n",
    "                \n",
    "        # put <EOS> token at the end of the chosen target sentence\n",
    "        # this token suggests when to stop creating a sequence\n",
    "        target_token_id.append(target_vocab_to_int['<EOS>'])\n",
    "            \n",
    "        # add each converted sentences in the final list\n",
    "        source_pass_id.append(source_token_id)\n",
    "        target_path_id.append(target_token_id)\n",
    "    \n",
    "    return source_pass_id, target_path_id"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Building The Network\n",
    "The model can be separated into 2 sub-models. The first sub-model is called as __[E]__ Encoder, and the second sub-model is called as __[D]__ Decoder. __[E]__ takes a raw input text data (passwords) just like any other RNN architectures do. At the end, __[E]__ outputs a neural representation (embedding). This is a very typical work, but you need to pay attention what this output really is. The output of __[E]__ is going to be the input data for __[D]__.\n",
    "\n",
    "That is why we call __[E]__ as Encoder and __[D]__ as Decoder. __[E]__ makes an output encoded in neural representational form, and we don't know what it really is. __[D]__ has the ability to look inside the __[E]__'s output, and it will create a totally different output data (corresponding path to a differnet password). \n",
    "\n",
    "6 functions represent the overall steps of the network:\n",
    "- __(1)__ define input parameters to the encoder model\n",
    "  - `enc_dec_model_inputs`\n",
    "- __(2)__ build encoder model\n",
    "  - `encoding_layer`\n",
    "- __(3)__ define input parameters to the decoder model\n",
    "  - `enc_dec_model_inputs`, `process_decoder_input`, `decoding_layer`\n",
    "- __(4)__ build decoder model for training\n",
    "  - `decoding_layer_train`\n",
    "- __(5)__ build decoder model for inference\n",
    "  - `decoding_layer_infer`\n",
    "- __(6)__ put (4) and (5) together\n",
    "  - `decoding_layer`\n",
    "- __(7)__ connect encoder and decoder models\n",
    "  - `pass2path_model` (`seq2seq` variant)\n",
    "- __(8)__ train and estimate loss and accuracy"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Parameters Definitions\n",
    "`enc_dec_model_inputs` function creates and returns parameters (TF placeholders) related to building model. \n",
    "\n",
    "- inputs placeholder will be fed with passwords data, and its shape is `[None, None]`. The first `None` means the batch size, and the batch size is unknown since the user can set it. The second `None` means the lengths of passwords. The maximum length of password is different from batch to batch, so it cannot be set with the exact number. \n",
    "  - An alternative is to set the lengths of every password to the maximum length across all passwords in every batch. No matter which method you choose, you need to add special character, `<PAD>` in empty positions. However, with the alternative option, there could be unnecessarily more `<PAD>` characters.\n",
    "  \n",
    "\n",
    "- targets placeholder is similar to inputs placeholder except that it will be fed with transition paths data.\n",
    "\n",
    "\n",
    "- target_sequence_length placeholder represents the lengths of each path, so the shape is `None`, a column tensor, which is the same number to the batch size. This particular value is required as an argument of TrainerHelper to build decoder model for training. We will see in (4).\n",
    "\n",
    "\n",
    "- max_target_len gets the maximum value out of lengths of all the target paths(sequences). We have the lengths of all the paths in target_sequence_length parameter. The way to get the maximum value from it is to use [tf.reduce_max](https://www.tensorflow.org/api_docs/python/tf/reduce_max). "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "def enc_dec_model_inputs():\n",
    "    '''\n",
    "    A function that creates and returns parameters (TF placeholders) related to building model. \n",
    "    - inputs placeholder will be fed with passwords data, and its shape is `[None, None]`.\n",
    "        The first `None` means the batch size, and the batch size is unknown since the user can set it. \n",
    "        The second `None` means the lengths of passwords. \n",
    "        The maximum length of password is different from batch to batch, so it cannot be set with the exact number. \n",
    "          - An alternative is to set the lengths of every password to the maximum length across all passwords in every batch.\n",
    "              No matter which method you choose, you need to add special character, `<PAD>` in empty positions. \n",
    "              However, with the alternative option, there could be unnecessarily more `<PAD>` characters.\n",
    "    - targets placeholder is similar to inputs placeholder except that it will be fed with transition paths data.\n",
    "    - target_sequence_length placeholder represents the lengths of each path, so the shape is `None`,\n",
    "        a column tensor, which is the same number to the batch size. \n",
    "        This particular value is required as an argument of TrainerHelper to build decoder model for training. \n",
    "    - max_target_len gets the maximum value out of lengths of all the target paths(sequences)\n",
    "    '''\n",
    "    inputs = tf.placeholder(tf.int32, [None, None], name='input')\n",
    "    targets = tf.placeholder(tf.int32, [None, None], name='targets') \n",
    "    \n",
    "    target_sequence_length = tf.placeholder(tf.int32, [None], name='target_sequence_length')\n",
    "    max_target_len = tf.reduce_max(target_sequence_length)    \n",
    "    \n",
    "    return inputs, targets, target_sequence_length, max_target_len"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`hyperparam_inputs` function creates and returns parameters (TF placeholders) related to hyper-parameters of the model, which are tunable.\n",
    "- lr_rate is learning rate\n",
    "- keep_prob is the keep probability for Dropouts (1 for testing, (0,1) for training)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def hyperparam_inputs():\n",
    "    '''\n",
    "    A function that creates and returns parameters (TF placeholders) related to hyper-parameters of the model, \n",
    "    which are tunable.\n",
    "    - lr_rate is learning rate\n",
    "    - keep_prob is the keep probability for Dropouts (1 for testing, (0,1) for training)\n",
    "    '''\n",
    "    lr_rate = tf.placeholder(tf.float32, name='lr_rate')\n",
    "    keep_prob = tf.placeholder(tf.float32, name='keep_prob')\n",
    "    return lr_rate, keep_prob"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Preprocess Decoder Input\n",
    "\n",
    "On the decoder side, there are 2 different kinds of input for training and inference purposes repectively. In the training phase, the input provided is the ground truth target, which still has to go through embedding. In the inference phase, however, the output of each time step will be the input for the next time step. They also need to be embeded and embedding vector should be shared between two different phases.\n",
    "\n",
    "Up next, we preprocess the target label data for the training phase. All we need to do is add `<GO>` special token in front of the whole target data. `<GO>` token practically means \"this is the start of the translation\". For this process, we'll use 3 functions from TensorFlow.\n",
    "- [TF strided_slice](https://www.tensorflow.org/api_docs/python/tf/strided_slice)\n",
    "  - extracts a strided slice of a tensor (generalized python array indexing).\n",
    "  - can be thought as splitting into multiple tensors with the striding window size from begin to end\n",
    "  - arguments: TF Tensor, Begin, End, Strides\n",
    "- [TF fill](https://www.tensorflow.org/api_docs/python/tf/concat)\n",
    "  - creates a tensor filled with a scalar value.\n",
    "  - arguments: TF Tensor (must be int32/int64), value to fill\n",
    "- [TF concat](https://www.tensorflow.org/api_docs/python/tf/fill)\n",
    "  - concatenates tensors along one dimension.\n",
    "  - arguments: a list of TF Tensor (tf.fill and after_slice in this case), axis=1\n",
    "    \n",
    "After preprocessing the target label data, we will embed it later when implementing decoding_layer function."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "def process_decoder_input(target_data, target_vocab_to_int, batch_size):\n",
    "    \"\"\"\n",
    "    Preprocess target data for encoding by adding <GO> token\n",
    "    return: Preprocessed target data\n",
    "    \"\"\"\n",
    "    #'<GO>' id\n",
    "    go_id = target_vocab_to_int['<GO>']\n",
    "    '''\n",
    "    extracts a slice of size (end-begin)/stride from the given input_ tensor.\n",
    "    Starting at the location specified by begin the slice continues by adding stride to the index until \n",
    "    all dimensions are not less than end. \n",
    "    '''\n",
    "    after_slice = tf.strided_slice(target_data, [0, 0], [batch_size, -1], [1, 1]) # input_, begin, end, strides\n",
    "    \n",
    "    after_concat = tf.concat( [tf.fill([batch_size, 1], go_id), after_slice], 1) # column tensor of \"go_id\" >> after_slice\n",
    "    \n",
    "    return after_concat"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Encoding\n",
    "\n",
    "The encoding sub-model consists of two different components. The first part is the embedding layer. Each character in a password will be represented with the number of features specified as `encoding_embedding_size`. This layer gives a much richer representation power for the chracters [useful explanation](https://stackoverflow.com/questions/40784656/tf-contrib-layers-embed-sequence-is-for-what/44280918#44280918). The second part is the RNN layer(s). We can make use of any kind of RNN related techniques or algorithms. For example, in this project, multiple LSTM cells are stacked together after dropout technique is applied. One can use different kinds of RNN cells such as GRUs.\n",
    "\n",
    "Embedding layer\n",
    "- [TF contrib.layers.embed_sequence](https://www.tensorflow.org/api_docs/python/tf/contrib/layers/embed_sequence)\n",
    "\n",
    "RNN layers\n",
    "- [TF contrib.rnn.LSTMCell](https://www.tensorflow.org/api_docs/python/tf/contrib/rnn/LSTMCell)\n",
    "  - simply specifies how many internal units it has\n",
    "- [TF contrib.rnn.DropoutWrapper](https://www.tensorflow.org/api_docs/python/tf/contrib/rnn/DropoutWrapper)\n",
    "  - wraps a cell with keep probability value \n",
    "- [TF contrib.rnn.MultiRNNCell](https://www.tensorflow.org/api_docs/python/tf/contrib/rnn/MultiRNNCell)\n",
    "  - stacks multiple RNN (type) cells\n",
    "  - [how this API is used in action?](https://github.com/tensorflow/tensorflow/blob/6947f65a374ebf29e74bb71e36fd82760056d82c/tensorflow/docs_src/tutorials/recurrent.md#stacking-multiple-lstms)\n",
    "  \n",
    "Encoding model\n",
    "- [TF nn.dynamic_rnn](https://www.tensorflow.org/api_docs/python/tf/nn/dynamic_rnn)\n",
    "  - put Embedding layer and RNN layer(s) all together"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "def encoding_layer(rnn_inputs, rnn_size, num_layers, keep_prob, \n",
    "                   source_vocab_size, \n",
    "                   encoding_embedding_size):\n",
    "    \"\"\"\n",
    "    This function creates the encoding layer of the network, by embedding the inputs and stacking RNN cells.\n",
    "    returns: tuple (RNN output, RNN state)\n",
    "    \"\"\"\n",
    "    embed = tf.contrib.layers.embed_sequence(rnn_inputs, \n",
    "                                             vocab_size=source_vocab_size, \n",
    "                                             embed_dim=encoding_embedding_size)\n",
    "    \n",
    "    # Here we can try to distribute the RNN layers across different GPUs\n",
    "    '''\n",
    "    devices = [\"/gpu:0\", \"/gpu:1\", \"/gpu:2\"]\n",
    "    cells = [DeviceCellWrapper(dev, tf.contrib.rnn.DropoutWrapper(tf.contrib.rnn.LSTMCell(rnn_size), keep_prob)) for \n",
    "            dev in devices]\n",
    "    stacked_cells = tf.contrib.rnn.MultiRNNCell(cells)\n",
    "    '''\n",
    "    stacked_cells = tf.contrib.rnn.MultiRNNCell(\n",
    "        [tf.contrib.rnn.DropoutWrapper(tf.contrib.rnn.LSTMCell(rnn_size), keep_prob) for _ in range(num_layers)]\n",
    "    )\n",
    "    \n",
    "    outputs, state = tf.nn.dynamic_rnn(cell=stacked_cells, \n",
    "                                       inputs=embed, \n",
    "                                       dtype=tf.float32)\n",
    "    return outputs, state"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Decoding - Training process\n",
    "\n",
    "The decoding process can be thought of 2 separate processes: training and inference. They share the same architecture and parameters, but they use different strategy to feed the shared model.\n",
    "\n",
    "While the encoder uses [TF contrib.layers.embed_sequence](https://www.tensorflow.org/api_docs/python/tf/contrib/layers/embed_sequence), it is not applicable to decoder even though it may require its inputs embeded. That is because the same embedding vector should be shared via training and inferece phases. [TF contrib.layers.embed_sequence](https://www.tensorflow.org/api_docs/python/tf/contrib/layers/embed_sequence) can only embed the prepared dataset before running. For the inference process dynamic embedding capability is required. It is impossible to embed the output from the inference process before running the model because the output of the current time step will be the input of the next time step.\n",
    "\n",
    "We'll later see how the embedding is done, but for now, one should keep in mind that both processes share the same embedding parameters. In training, embedded input is used and in inference, only the embedding parameters are used (from the training stage).\n",
    "\n",
    "Let's see to the training part first. \n",
    "- [`tf.contrib.seq2seq.TrainingHelper`](https://www.tensorflow.org/api_docs/python/tf/contrib/seq2seq/TrainingHelper)\n",
    "  - TrainingHelper is where we pass the embeded input. As the name indicates, this is only a helper instance. This instance should be delivered to the BasicDecoder, which is the actual process of building the decoder model.\n",
    "- [`tf.contrib.seq2seq.BasicDecoder`](https://www.tensorflow.org/api_docs/python/tf/contrib/seq2seq/BasicDecoder)\n",
    "  - BasicDecoder builds the decoder model. It means it connects the RNN layer(s) on the decoder side and the input prepared by TrainingHelper.\n",
    "- [`tf.contrib.seq2seq.dynamic_decode`](https://www.tensorflow.org/api_docs/python/tf/contrib/seq2seq/dynamic_decode)\n",
    "  - dynamic_decode unrolls the decoder model so that actual prediction can be retrieved by BasicDecoder for each time step."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "def decoding_layer_train(encoder_state, dec_cell, dec_embed_input, \n",
    "                         target_sequence_length, max_summary_length, \n",
    "                         output_layer, keep_prob):\n",
    "    \"\"\"\n",
    "    Creates a training process for the decoding layer \n",
    "    returns: BasicDecoderOutput containing training logits and sample_id\n",
    "    \"\"\"\n",
    "    dec_cell = tf.contrib.rnn.DropoutWrapper(dec_cell, \n",
    "                                             output_keep_prob=keep_prob)\n",
    "    \n",
    "    # only for the input layer\n",
    "    helper = tf.contrib.seq2seq.TrainingHelper(inputs=dec_embed_input, \n",
    "                                               sequence_length=target_sequence_length)\n",
    "    \n",
    "    decoder = tf.contrib.seq2seq.BasicDecoder(cell=dec_cell, \n",
    "                                              helper=helper, \n",
    "                                              initial_state=encoder_state, \n",
    "                                              output_layer=output_layer)\n",
    "    # output_layer: optional layer to apply to the RNN output prior to storing the result or sampling.\n",
    "\n",
    "    # unrolling the decoder layer\n",
    "    outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(decoder, \n",
    "                                                      impute_finished=True, \n",
    "                                                      maximum_iterations=max_summary_length)\n",
    "    # impute_finished: If True, then states for batch entries which are marked as finished get copied through\n",
    "    # and the corresponding outputs get zeroed out. \n",
    "    # This causes some slowdown at each time step, but ensures that the final state and outputs have the correct values\n",
    "    # and that backprop ignores time steps that were marked as finished.\n",
    "    return outputs"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Decoding - Inference process\n",
    "\n",
    "- [`tf.contrib.seq2seq.GreedyEmbeddingHelper`](https://www.tensorflow.org/api_docs/python/tf/contrib/seq2seq/GreedyEmbeddingHelper)\n",
    "  - GreedyEmbeddingHelper dynamically takes the output of the current step and give it to the next time step's input. In order to embed each input result dynamically, embedding parameters (just a bunch of weight values) should be provided. Along with it, GreedyEmbeddingHelper asks to give the `start_of_sequence_id` for the same size as the batch size and `end_of_sequence_id`.\n",
    "- [`tf.contrib.seq2seq.BasicDecoder`](https://www.tensorflow.org/api_docs/python/tf/contrib/seq2seq/BasicDecoder)\n",
    "  - same as described in the training process section\n",
    "- [`tf.contrib.seq2seq.dynamic_decode`](https://www.tensorflow.org/api_docs/python/tf/contrib/seq2seq/dynamic_decode)\n",
    "  - same as described in the training process section\n",
    "- [`tf.contrib.seq2seq.BeamSearchDecoder`](https://www.tensorflow.org/api_docs/python/tf/contrib/seq2seq/BeamSearchDecoder)\n",
    " - BeamSearch sampling decoder, outputs top K predictions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "def decoding_layer_infer(encoder_state, dec_cell, dec_embeddings, start_of_sequence_id,\n",
    "                         end_of_sequence_id, max_target_sequence_length,\n",
    "                         vocab_size, output_layer, batch_size, keep_prob):\n",
    "    \"\"\"\n",
    "    Create a inference process in decoding layer \n",
    "    return: BasicDecoderOutput containing inference logits and sample_id\n",
    "    \"\"\"\n",
    "    dec_cell = tf.contrib.rnn.DropoutWrapper(dec_cell, \n",
    "                                             output_keep_prob=keep_prob)\n",
    "    \n",
    "    helper = tf.contrib.seq2seq.GreedyEmbeddingHelper(embedding=dec_embeddings, \n",
    "                                                      start_tokens=tf.fill([batch_size], start_of_sequence_id), \n",
    "                                                      end_token=end_of_sequence_id)\n",
    "    # GreedyEmbeddingHelper uses the argmax of the output (treated as logits) \n",
    "    # and passes the result through an embedding layer to get the next input.\n",
    "    \n",
    "    decoder = tf.contrib.seq2seq.BasicDecoder(dec_cell, \n",
    "                                              helper, \n",
    "                                              encoder_state, \n",
    "                                              output_layer)\n",
    "    \n",
    "    outputs, _, _ = tf.contrib.seq2seq.dynamic_decode(decoder, \n",
    "                                                      impute_finished=True, \n",
    "                                                      maximum_iterations=max_target_sequence_length)\n",
    "    return outputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "def decoding_layer_infer_beam(encoder_state, dec_cell, dec_embeddings, start_of_sequence_id,\n",
    "                         end_of_sequence_id, max_target_sequence_length,\n",
    "                         vocab_size, output_layer, batch_size, keep_prob, beam_width=10):\n",
    "    \"\"\"\n",
    "    Create a inference process in decoding layer using Beam Search\n",
    "    return: BasicDecoderOutput containing inference logits and sample_id\n",
    "    \"\"\"\n",
    "    dec_cell = tf.contrib.rnn.DropoutWrapper(dec_cell, \n",
    "                                             output_keep_prob=keep_prob)\n",
    "    # Consider changing 128 to batch_size\n",
    "    \n",
    "    decoder = tf.contrib.seq2seq.BeamSearchDecoder(cell = dec_cell,\n",
    "                                                   embedding = dec_embeddings,\n",
    "                                                   start_tokens = tf.tile(tf.constant([start_of_sequence_id], dtype=tf.int32), [batch_size]),\n",
    "                                                   end_token = end_of_sequence_id,\n",
    "                                                   initial_state = tf.contrib.seq2seq.tile_batch(encoder_state, multiplier=beam_width),\n",
    "                                                   beam_width = beam_width,\n",
    "                                                   output_layer = output_layer,\n",
    "                                                   length_penalty_weight = 0.0)\n",
    "    outputs, _, final_sequence_lengths = tf.contrib.seq2seq.dynamic_decode(decoder, \n",
    "                                                      impute_finished=False, \n",
    "                                                      maximum_iterations=max_target_sequence_length)\n",
    "    return outputs, final_sequence_lengths"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Building the Decoding Layer\n",
    "\n",
    "__Embed the target sequences__\n",
    "\n",
    "- [TF contrib.layers.embed_sequence](https://www.tensorflow.org/api_docs/python/tf/contrib/layers/embed_sequence) creates an internal representation of the embedding parameter (we cannot look into or retrieve it). Rather, we need to create an embedding parameter manually by [TF Variable](https://www.tensorflow.org/api_docs/python/tf/Variable). \n",
    "\n",
    "- Manually created embedding parameter is used for training phase to convert provided target data (sequence of paths) by [TF nn.embedding_lookup](https://www.tensorflow.org/api_docs/python/tf/nn/embedding_lookup) before the training is run. [TF nn.embedding_lookup](https://www.tensorflow.org/api_docs/python/tf/nn/embedding_lookup) with manually created embedding parameters returns the similar result to the [TF contrib.layers.embed_sequence](https://www.tensorflow.org/api_docs/python/tf/contrib/layers/embed_sequence). For the inference process, whenever the output of the current time step is calculated via decoder, it will be embeded by the shared embedding parameter and become the input for the next time step. You only need to provide the embedding parameter to the GreedyEmbeddingHelper, then it will help the process.\n",
    "\n",
    "- [How embedding_lookup works?](https://stackoverflow.com/questions/34870614/what-does-tf-nn-embedding-lookup-function-do)\n",
    "  - In short, it selects specified rows\n",
    "  \n",
    "- Note: Please be careful about setting the variable scope. As mentioned previously, parameters/variables are shared between training and inference processes. Sharing can be specified via [tf.variable_scope](https://www.tensorflow.org/api_docs/python/tf/variable_scope).\n",
    "\n",
    "__Construct the decoder RNN layer(s)__\n",
    "- The number of RNN layer(s) in the decoder model must be equal to the number of RNN layer(s) in the encoder model.\n",
    "\n",
    "__Create an output layer to map the outputs of the decoder to the elements of our vocabulary__\n",
    "- This is just a fully connected (FC) layer to get probabilities of occurance of each words at the end."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "def decoding_layer(dec_input, encoder_state,\n",
    "                   target_sequence_length, max_target_sequence_length,\n",
    "                   rnn_size,\n",
    "                   num_layers, target_vocab_to_int, target_vocab_size,\n",
    "                   batch_size, keep_prob, decoding_embedding_size):\n",
    "    \"\"\"\n",
    "    Create decoding layer by assembling the training and inference decoders.\n",
    "    return: Tuple of (Training BasicDecoderOutput, Inference BasicDecoderOutput)\n",
    "    \"\"\"\n",
    "    target_vocab_size = len(target_vocab_to_int)\n",
    "    dec_embeddings = tf.Variable(tf.random_uniform([target_vocab_size, decoding_embedding_size]))\n",
    "    dec_embed_input = tf.nn.embedding_lookup(dec_embeddings, dec_input)\n",
    "    \n",
    "    cells = tf.contrib.rnn.MultiRNNCell([tf.contrib.rnn.LSTMCell(rnn_size) for _ in range(num_layers)])\n",
    "    \n",
    "    with tf.variable_scope(\"decode\"):\n",
    "        output_layer = tf.layers.Dense(target_vocab_size)\n",
    "        train_output = decoding_layer_train(encoder_state, \n",
    "                                            cells, \n",
    "                                            dec_embed_input, \n",
    "                                            target_sequence_length, \n",
    "                                            max_target_sequence_length, \n",
    "                                            output_layer, \n",
    "                                            keep_prob)\n",
    "\n",
    "    with tf.variable_scope(\"decode\", reuse=True):\n",
    "        infer_output = decoding_layer_infer(encoder_state, \n",
    "                                            cells, \n",
    "                                            dec_embeddings, \n",
    "                                            target_vocab_to_int['<GO>'], \n",
    "                                            target_vocab_to_int['<EOS>'], \n",
    "                                            max_target_sequence_length, \n",
    "                                            target_vocab_size, \n",
    "                                            output_layer,\n",
    "                                            batch_size,\n",
    "                                            keep_prob)\n",
    "\n",
    "    return (train_output, infer_output)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "def decoding_layer_beam(dec_input, encoder_state,\n",
    "                   target_sequence_length, max_target_sequence_length,\n",
    "                   rnn_size,\n",
    "                   num_layers, target_vocab_to_int, target_vocab_size,\n",
    "                   batch_size, keep_prob, decoding_embedding_size, beam_width=10):\n",
    "    \"\"\"\n",
    "    Create decoding layer by assembling the training and inference beam search decoders.\n",
    "    return: Tuple of (Training BasicDecoderOutput, Inference BasicDecoderOutput)\n",
    "    \"\"\"\n",
    "    target_vocab_size = len(target_vocab_to_int)\n",
    "    dec_embeddings = tf.Variable(tf.random_uniform([target_vocab_size, decoding_embedding_size]))\n",
    "    dec_embed_input = tf.nn.embedding_lookup(dec_embeddings, dec_input)\n",
    "    \n",
    "    cells = tf.contrib.rnn.MultiRNNCell([tf.contrib.rnn.LSTMCell(rnn_size) for _ in range(num_layers)])\n",
    "    \n",
    "    with tf.variable_scope(\"decode\"):\n",
    "        output_layer = tf.layers.Dense(target_vocab_size)\n",
    "        train_output = decoding_layer_train(encoder_state, \n",
    "                                            cells, \n",
    "                                            dec_embed_input, \n",
    "                                            target_sequence_length, \n",
    "                                            max_target_sequence_length, \n",
    "                                            output_layer, \n",
    "                                            keep_prob)\n",
    "\n",
    "    with tf.variable_scope(\"decode\", reuse=True):\n",
    "        infer_output, infer_lengths = decoding_layer_infer_beam(encoder_state, \n",
    "                                                                cells, \n",
    "                                                                dec_embeddings, \n",
    "                                                                target_vocab_to_int['<GO>'], \n",
    "                                                                target_vocab_to_int['<EOS>'], \n",
    "                                                                max_target_sequence_length, \n",
    "                                                                target_vocab_size, \n",
    "                                                                output_layer,\n",
    "                                                                batch_size,\n",
    "                                                                keep_prob,\n",
    "                                                                beam_width)\n",
    "\n",
    "    return (train_output, infer_output, infer_lengths)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Assembling the Pass2Path model\n",
    "\n",
    "Assembling all the previous defined functions into one model: `encoding_layer`, `process_decoder_input`, and `decoding_layer` are put together to build the big picture, Password to Pass model. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pass2path_model(input_data, target_data, keep_prob, batch_size,\n",
    "                  target_sequence_length,\n",
    "                  max_target_sentence_length,\n",
    "                  source_vocab_size, target_vocab_size,\n",
    "                  enc_embedding_size, dec_embedding_size,\n",
    "                  rnn_size, num_layers, target_vocab_to_int):\n",
    "    \"\"\"\n",
    "    Assemble the Password-to-Path model\n",
    "    return: Tuple of (Training BasicDecoderOutput, Inference BasicDecoderOutput)\n",
    "    \"\"\"\n",
    "    enc_outputs, enc_states = encoding_layer(input_data, \n",
    "                                             rnn_size, \n",
    "                                             num_layers, \n",
    "                                             keep_prob, \n",
    "                                             source_vocab_size, \n",
    "                                             enc_embedding_size)\n",
    "    \n",
    "    dec_input = process_decoder_input(target_data, \n",
    "                                      target_vocab_to_int, \n",
    "                                      batch_size)\n",
    "    \n",
    "    train_output, infer_output = decoding_layer(dec_input, \n",
    "                                                enc_states, \n",
    "                                                target_sequence_length, \n",
    "                                                max_target_sentence_length,\n",
    "                                                rnn_size,\n",
    "                                                num_layers,\n",
    "                                                target_vocab_to_int,\n",
    "                                                target_vocab_size,\n",
    "                                                batch_size,\n",
    "                                                keep_prob,\n",
    "                                                dec_embedding_size)\n",
    "    \n",
    "    return train_output, infer_output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pass2path_model_beam(input_data, target_data, keep_prob, batch_size,\n",
    "                  target_sequence_length,\n",
    "                  max_target_sentence_length,\n",
    "                  source_vocab_size, target_vocab_size,\n",
    "                  enc_embedding_size, dec_embedding_size,\n",
    "                  rnn_size, num_layers, target_vocab_to_int, beam_width=10):\n",
    "    \"\"\"\n",
    "    Assemble the Password-to-Path model\n",
    "    return: Tuple of (Training BasicDecoderOutput, Inference FinalBeamSearchDecoderOutput)\n",
    "    \"\"\"\n",
    "    enc_outputs, enc_states = encoding_layer(input_data, \n",
    "                                             rnn_size, \n",
    "                                             num_layers, \n",
    "                                             keep_prob, \n",
    "                                             source_vocab_size, \n",
    "                                             enc_embedding_size)\n",
    "    \n",
    "    dec_input = process_decoder_input(target_data, \n",
    "                                      target_vocab_to_int, \n",
    "                                      batch_size)\n",
    "    \n",
    "    train_output, infer_output, infer_lengths = decoding_layer_beam(dec_input, \n",
    "                                                    enc_states, \n",
    "                                                    target_sequence_length, \n",
    "                                                    max_target_sentence_length,\n",
    "                                                    rnn_size,\n",
    "                                                    num_layers,\n",
    "                                                    target_vocab_to_int,\n",
    "                                                    target_vocab_size,\n",
    "                                                    batch_size,\n",
    "                                                    keep_prob,\n",
    "                                                    dec_embedding_size, beam_width)\n",
    "    \n",
    "    return train_output, infer_output, infer_lengths"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Neural Network Training"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Building the Graph\n",
    "`pass2path_model` function creates the model. It defines how the feedforward and backpropagation should flow. The last step for this model to be trainable is to decide and apply what optimization algorithms to use. In this section, [TF contrib.seq2seq.sequence_loss](https://www.tensorflow.org/api_docs/python/tf/contrib/seq2seq/sequence_loss) is used to calculate the loss, then [TF train.AdamOptimizer](https://www.tensorflow.org/api_docs/python/tf/train/AdamOptimizer) is applied to calculate the gradient descent on the loss.\n",
    "\n",
    "__Load data from the checkpoint__\n",
    "- (source_int_text, target_int_text) are the input data, and (source_vocab_to_int, target_vocab_to_int) is the dictionary to lookup the index number of each password/transition. We'll have to define CSV readers (tf.TextReader)\n",
    "- max_target_path_length is the length of the longest path from the source input data. This will be used for GreedyEmbeddingHelper when building inference process in the decoder mode.\n",
    "\n",
    "__Create inputs__\n",
    "- inputs (input_data, targets, target_path_length, max_target_path_length) from enc_dec_model_inputs function\n",
    "- inputs (lr, keep_prob) from hyperparam_inputs function\n",
    "\n",
    "__Assemble pass2path model__\n",
    "- build the model by pass2path_model function. It will return train_logits(logits to calculate the loss) and inference_logits(logits from prediction).\n",
    "\n",
    "__Cost function__\n",
    "- [TF contrib.seq2seq.sequence_loss](https://www.tensorflow.org/api_docs/python/tf/contrib/seq2seq/sequence_loss) is used. This loss function is just a weighted softmax cross entropy loss function, but it is particularly designed to be applied in time series model (RNN). Weights should be explicitly provided as an argument, and it can be created by [TF sequence_mask](https://www.tensorflow.org/api_docs/python/tf/sequence_mask). In this project, [TF sequence_mask](https://www.tensorflow.org/api_docs/python/tf/sequence_mask) creates \\[batch_size, max_target_sequence_length\\] size of variable, then maks only the first target_sequence_length number of elements to 1. It means <PAD\\> parts will have less weight than others.\n",
    "\n",
    "__Optimizer__\n",
    "- [TF train.AdamOptimizer](https://www.tensorflow.org/api_docs/python/tf/train/AdamOptimizer) is used, and this is where the learning rate should be specified. You can choose other algorithms as well, this is just a choice.\n",
    "\n",
    "__Gradient Clipping__\n",
    "- Since recurrent neural networks is notorious about vanishing/exploding gradient, gradient clipping technique is believed to improve the issues. \n",
    "- The concept is really easy. You decide thresholds to keep the gradient to be in a certain boundary. In this project, the range of the threshold is between -1 and 1.\n",
    "- Now, you need to apply this conceptual knowledge to the TensorFlow code. Luckily, there is the official guide for this [TF Gradient Clipping How?](https://www.tensorflow.org/api_guides/python/train#Gradient_Clipping). In breif, you get the gradient values from the optimizer manually by calling [compute_gradients](https://www.tensorflow.org/api_docs/python/tf/train/Optimizer#compute_gradients), then manipulate the gradient values with [clip_by_value](https://www.tensorflow.org/api_docs/python/tf/clip_by_value). Lastly, you need to put back the modified gradients into the optimizer by calling [apply_gradients](https://www.tensorflow.org/api_docs/python/tf/train/Optimizer#apply_gradients)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Hyperparameters\n",
    "display_step = 10\n",
    "\n",
    "epochs = 100\n",
    "batch_size = 10\n",
    "\n",
    "rnn_size = 128\n",
    "num_layers = 3\n",
    "\n",
    "encoding_embedding_size = 200\n",
    "decoding_embedding_size = 200\n",
    "beam_width = 10\n",
    "edit_distance = 3\n",
    "\n",
    "learning_rate = 0.001\n",
    "keep_probability = 0.8 # or 0.5"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [],
   "source": [
    "def _parse_line(line):\n",
    "    '''\n",
    "    This helper function parses a line from the CSV dataset to a dictionary and a target.\n",
    "    '''\n",
    "    # Metadata describing the text columns\n",
    "    \n",
    "    COLUMNS = ['pass_1', 'pass_2', 'path']\n",
    "    FIELD_DEFAULTS = [[\"\"], [\"\"], [\"\"]]\n",
    "    \n",
    "    # Decode the line into its fields\n",
    "    fields = tf.decode_csv(line, FIELD_DEFAULTS)\n",
    "\n",
    "    # Pack the result into a dictionary\n",
    "    features = dict(zip(COLUMNS,fields))\n",
    "\n",
    "    # Separate the label from the features\n",
    "    path = features.pop('path')\n",
    "\n",
    "    return features, path\n",
    "\n",
    "def csv_input_fn(csv_path, batch_size, skip_lines):\n",
    "    '''\n",
    "    This function builds a batch from the dataset in the CSV file.\n",
    "    '''\n",
    "    # Create a dataset containing the text lines.\n",
    "    dataset = tf.data.TextLineDataset(csv_path).skip(skip_lines)\n",
    "\n",
    "    # Parse each line.\n",
    "    dataset = dataset.map(_parse_line)\n",
    "\n",
    "    # Shuffle, repeat, and batch the examples.\n",
    "    dataset = dataset.shuffle(1000).repeat().batch(batch_size)\n",
    "\n",
    "    # Return the dataset.\n",
    "    return dataset\n",
    "\n",
    "def csv_input_fn_filter(csv_path, batch_size, skip_lines, edit_distance, limit = -1):\n",
    "    '''\n",
    "    This function builds a batch from the dataset in the CSV file, and filters out\n",
    "    samples that do not satisfy the edit distance condition.\n",
    "    '''\n",
    "    # Create a dataset containing the text lines.\n",
    "    dataset = tf.data.TextLineDataset(csv_path).skip(skip_lines).take(limit)\n",
    "\n",
    "    # Parse each line.\n",
    "    dataset = dataset.map(_parse_line)\n",
    "    \n",
    "    # Filter\n",
    "    dataset = dataset.filter(lambda feat, path : tf.less_equal(tf.size((tf.string_split(\n",
    "        tf.reshape(path, shape=(1,)), \" []\"))), edit_distance))\n",
    "\n",
    "    # Shuffle, repeat, and batch the examples.\n",
    "    dataset = dataset.shuffle(1000).repeat().batch(batch_size)\n",
    "\n",
    "    # Return the dataset.\n",
    "    return dataset\n",
    "\n",
    "def pad_sequence_batch(sequence_batch, pad_int):\n",
    "    \"\"\"\n",
    "    Pad sequences with <PAD> so that each sequence of a batch has the same length\n",
    "    \"\"\"\n",
    "    max_sequence = max([len(seq) for seq in sequence_batch])\n",
    "    return [seq + [pad_int] * (max_sequence - len(seq)) for seq in sequence_batch]\n",
    "\n",
    "\n",
    "def get_batches(sources, targets, batch_size, source_pad_int, target_pad_int):\n",
    "    \"\"\"\n",
    "    Batch targets, sources, and the lengths of their sentences together\n",
    "    \"\"\"\n",
    "    for batch_i in range(0, len(sources)//batch_size):\n",
    "        start_i = batch_i * batch_size\n",
    "\n",
    "        # Slice the right amount for the batch\n",
    "        sources_batch = sources[start_i:start_i + batch_size]\n",
    "        targets_batch = targets[start_i:start_i + batch_size]\n",
    "\n",
    "        # Pad\n",
    "        pad_sources_batch = np.array(pad_sentence_batch(sources_batch, source_pad_int))\n",
    "        pad_targets_batch = np.array(pad_sentence_batch(targets_batch, target_pad_int))\n",
    "\n",
    "        # Need the lengths for the _lengths parameters\n",
    "        pad_targets_lengths = []\n",
    "        for target in pad_targets_batch:\n",
    "            pad_targets_lengths.append(len(target))\n",
    "\n",
    "        pad_source_lengths = []\n",
    "        for source in pad_sources_batch:\n",
    "            pad_source_lengths.append(len(source))\n",
    "\n",
    "        yield pad_sources_batch, pad_targets_batch, pad_source_lengths, pad_targets_lengths\n",
    "        \n",
    "def get_batches_from_file(sess, filename, batch_size, source_vocab_to_int, target_vocab_to_int):\n",
    "    \"\"\"\n",
    "    Batch targets, sources, and the lengths of their sequences together, where\n",
    "    the dataset originates from a CSV file.\n",
    "    \"\"\"\n",
    "    num_samples = 0\n",
    "    with open(filename) as csv_file:\n",
    "        csv_reader = csv.reader(csv_file, delimiter=',')\n",
    "        num_samples = sum(1 for row in csv_reader)\n",
    "    print(\"# Samples: {}\".format(num_samples))\n",
    "    dataset = csv_input_fn(filename, batch_size, skip_lines = 0)\n",
    "    iterator = dataset.make_initializable_iterator()\n",
    "    next_element = iterator.get_next()\n",
    "    sess.run(iterator.initializer)\n",
    "    total_batches = num_samples//batch_size\n",
    "    print(\"Total batches: {}\".format(total_batches))\n",
    "    \n",
    "    for batch_i in range(0, total_batches):\n",
    "#         start_i = batch_i * batch_size\n",
    "        batch = sess.run(next_element)\n",
    "        pass_batch = [d.decode('utf-8') for d in batch[0]['pass_1']]\n",
    "        path_batch = [json.loads(j) for j in [d.decode('utf-8') for d in batch[1]]]\n",
    "#         if (len(pass_batch) != len(path_batch)):\n",
    "#             print(pass_batch)\n",
    "#             print(path_batch)\n",
    "        \n",
    "        pass_batch_ids, path_batch_ids = input_to_ids(pass_batch, path_batch,\n",
    "                                                      source_vocab_to_int, target_vocab_to_int)\n",
    "        \n",
    "        source_pad_int = source_vocab_to_int['<PAD>']\n",
    "        target_pad_int = target_vocab_to_int['<PAD>']\n",
    "\n",
    "        # Pad\n",
    "        pad_sources_batch = np.array(pad_sequence_batch(pass_batch_ids, source_pad_int))\n",
    "        pad_targets_batch = np.array(pad_sequence_batch(path_batch_ids, target_pad_int))\n",
    "\n",
    "        # Need the lengths for the _lengths parameters\n",
    "        pad_targets_lengths = []\n",
    "        for target in pad_targets_batch:\n",
    "            pad_targets_lengths.append(len(target))\n",
    "\n",
    "        pad_source_lengths = []\n",
    "        for source in pad_sources_batch:\n",
    "            pad_source_lengths.append(len(source))\n",
    "\n",
    "        yield pad_sources_batch, pad_targets_batch, pad_source_lengths, pad_targets_lengths\n",
    "\n",
    "def get_batches_from_file_filter(sess, filename, batch_size, source_vocab_to_int, target_vocab_to_int, edit_distance=3):\n",
    "    \"\"\"\n",
    "    Batch targets, sources, and the lengths of their sequences together, where\n",
    "    the dataset originates from a CSV file. It filters out samples that do not satisfy the\n",
    "    edit distance condition.\n",
    "    \"\"\"\n",
    "    num_samples = 0\n",
    "    with open(filename) as csv_file:\n",
    "        csv_reader = csv.reader(csv_file, delimiter=',')\n",
    "        num_samples = sum(1 for row in csv_reader)\n",
    "#     print(\"# Samples: {}\".format(num_samples))\n",
    "    dataset = csv_input_fn_filter(filename, batch_size, skip_lines = 0, edit_distance = edit_distance)\n",
    "    iterator = dataset.make_initializable_iterator()\n",
    "    next_element = iterator.get_next()\n",
    "    sess.run(iterator.initializer)\n",
    "    total_batches = num_samples//batch_size\n",
    "#     print(\"Total batches: {}\".format(total_batches))\n",
    "    \n",
    "    for batch_i in range(0, total_batches):\n",
    "#         start_i = batch_i * batch_size\n",
    "        batch = sess.run(next_element)\n",
    "        pass_batch = [d.decode('utf-8') for d in batch[0]['pass_1']]\n",
    "        path_batch = [json.loads(j) for j in [d.decode('utf-8') for d in batch[1]]]\n",
    "#         if (len(pass_batch) != len(path_batch)):\n",
    "#             print(pass_batch)\n",
    "#             print(path_batch)\n",
    "        \n",
    "        pass_batch_ids, path_batch_ids = input_to_ids(pass_batch, path_batch,\n",
    "                                                      source_vocab_to_int, target_vocab_to_int)\n",
    "        \n",
    "        source_pad_int = source_vocab_to_int['<PAD>']\n",
    "        target_pad_int = target_vocab_to_int['<PAD>']\n",
    "\n",
    "        # Pad\n",
    "        pad_sources_batch = np.array(pad_sequence_batch(pass_batch_ids, source_pad_int))\n",
    "        pad_targets_batch = np.array(pad_sequence_batch(path_batch_ids, target_pad_int))\n",
    "\n",
    "        # Need the lengths for the _lengths parameters\n",
    "        pad_targets_lengths = []\n",
    "        for target in pad_targets_batch:\n",
    "            pad_targets_lengths.append(len(target))\n",
    "\n",
    "        pad_source_lengths = []\n",
    "        for source in pad_sources_batch:\n",
    "            pad_source_lengths.append(len(source))\n",
    "\n",
    "        yield pad_sources_batch, pad_targets_batch, pad_source_lengths, pad_targets_lengths\n",
    "        \n",
    "def preprocess_batch(batch, source_vocab_to_int, target_vocab_to_int):\n",
    "#     kb = Keyboard()\n",
    "    pass_batch = [kb.word_to_keyseq(d.decode('utf-8')) for d in batch[0]['pass_1']]\n",
    "    pass2_batch = [kb.word_to_keyseq(d.decode('utf-8')) for d in batch[0]['pass_2']]\n",
    "    path_batch = [json.loads(j) for j in [d.decode('utf-8') for d in batch[1]]]\n",
    "    pass_batch_ids, path_batch_ids = input_to_ids(pass_batch, path_batch,\n",
    "                                                      source_vocab_to_int, target_vocab_to_int)\n",
    "    source_pad_int = source_vocab_to_int['<PAD>']\n",
    "    target_pad_int = target_vocab_to_int['<PAD>']\n",
    "    # Pad\n",
    "    pad_sources_batch = np.array(pad_sequence_batch(pass_batch_ids, source_pad_int))\n",
    "    pad_targets_batch = np.array(pad_sequence_batch(path_batch_ids, target_pad_int))\n",
    "\n",
    "    # Need the lengths for the _lengths parameters\n",
    "    pad_targets_lengths = []\n",
    "    for target in pad_targets_batch:\n",
    "        pad_targets_lengths.append(len(target))\n",
    "\n",
    "    pad_source_lengths = []\n",
    "    for source in pad_sources_batch:\n",
    "        pad_source_lengths.append(len(source))\n",
    "    return (pad_sources_batch, pad_targets_batch, pad_source_lengths, pad_targets_lengths, pass_batch, pass2_batch)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Train\n",
    "\n",
    "`get_accuracy`\n",
    "- compare the lengths of target(label) and logits(prediction)\n",
    "- add (PAD) 0s or (EOS) 1s at the end of the ones having the shorter length\n",
    "  - `[(0,0),(0,max_seq - target.shape[1])]` indicates the 2D array. The first (0,0) means no padding for the first dimension. The second (0, ...) means there is no pads in front of the second dimension but pads at the end. And pad as many times as ... .\n",
    "- above process is to makes two entities to have the same shape (length)\n",
    "- finally, returns the average of where the target and logits have the same value (1)\n",
    "\n",
    "[numpy pad function](https://docs.scipy.org/doc/numpy-1.14.0/reference/generated/numpy.pad.html)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 31,
   "metadata": {},
   "outputs": [],
   "source": [
    "def get_accuracy(target, logits):\n",
    "    \"\"\"\n",
    "    Calculate accuracy\n",
    "    \"\"\"\n",
    "    max_seq = max(target.shape[1], logits.shape[1])\n",
    "    if max_seq - target.shape[1]:\n",
    "        target = np.pad(\n",
    "            target,\n",
    "            [(0,0),(0,max_seq - target.shape[1])],\n",
    "            'constant')\n",
    "    if max_seq - logits.shape[1]:\n",
    "        logits = np.pad(\n",
    "            logits,\n",
    "            [(0,0),(0,max_seq - logits.shape[1])],\n",
    "            'constant')\n",
    "\n",
    "    return np.mean(np.equal(target, logits))\n",
    "\n",
    "def get_accuracy_beam(target, logits, pad_int=0):\n",
    "    \"\"\"\n",
    "    Calculate accuracy of BeamSearch output as follows: if one of the K outputs is correct,\n",
    "    it counts as a correct prediction (positive contribution to the accuracy).\n",
    "    \"logits\" are now of the shape: [batches, max_seq, K]\n",
    "    \"\"\"\n",
    "#     print(target.shape)\n",
    "#     print(logits.shape)\n",
    "    max_seq = max(target.shape[1], logits.shape[1])\n",
    "    if max_seq - target.shape[1]:\n",
    "        target = np.pad(\n",
    "            target,\n",
    "            [(0,0),(0,max_seq - target.shape[1])],\n",
    "            'constant', constant_values = pad_int)\n",
    "    if max_seq - logits.shape[1]:\n",
    "        logits = np.pad(\n",
    "            logits,\n",
    "            [(0,0), (0,max_seq - logits.shape[1]), (0,0)],\n",
    "            'constant', constant_values = pad_int)\n",
    "#     print(target)\n",
    "#     print(logits)\n",
    "    acc = [np.mean(np.equal(target, np.reshape(logits[:,:,i], target.shape))) for i in range(logits.shape[2])]\n",
    "#     if (max(acc) > 0.6):\n",
    "#         print(target)\n",
    "#         print(np.reshape(np.transpose(logits[:,:,acc.index(max(acc))]), target.shape))\n",
    "    return max(acc)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Save Parameters\n",
    "Save the `batch_size` and `save_path` parameters for inference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "def save_params(params):\n",
    "    with open('params.p', 'wb') as out_file:\n",
    "        pickle.dump(params, out_file)\n",
    "\n",
    "\n",
    "def load_params():\n",
    "    with open('params.p', mode='rb') as in_file:\n",
    "        return pickle.load(in_file)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Save parameters for checkpoint\n",
    "save_params(save_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load Checkpoint and Decode Path\n",
    "This will decode `path` from Integers to Human-Readable Path and output the decoded password."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 56,
   "metadata": {},
   "outputs": [],
   "source": [
    "def pass_to_seq(password, vocab_to_int):\n",
    "    '''\n",
    "    This function transforms password to sequence of integers, in order to make the tested password\n",
    "    applicable to the network's input.\n",
    "    '''\n",
    "    results = []\n",
    "    for c in list(password):\n",
    "        if c in vocab_to_int:\n",
    "            results.append(vocab_to_int[c])\n",
    "        else:\n",
    "            results.append(vocab_to_int['<UNK>'])\n",
    "            \n",
    "    return results\n",
    "def path2word_kb_feasible(word, path, print_path=False):\n",
    "    '''\n",
    "    This function decodes the word in which the given path transitions the input word into.\n",
    "    This is the KeyPress version, which handles the keyboard representations.\n",
    "    If one of the parts components is not feasible (e.g removing a char from out of range index), it skips it\n",
    "    Input parameters: original word, transition path\n",
    "    Output: decoded word\n",
    "    '''\n",
    "#     kb = Keyboard()\n",
    "    word = kb.word_to_keyseq(word)\n",
    "    if not path:\n",
    "        return kb.keyseq_to_word(word)\n",
    "#     path = [literal_eval(p) for p in path]\n",
    "    if (print_path):\n",
    "        print(path)\n",
    "#     print(type(path))\n",
    "#     print(word)\n",
    "    final_word = []\n",
    "    word_len = len(word)\n",
    "    path_len = len(path)\n",
    "    i = 0\n",
    "    j = 0\n",
    "    while (i < word_len or j < path_len):\n",
    "        if ((j < path_len and path[j][2] == i) or (i >= word_len and path[j][2] >= i)):\n",
    "            if (path[j][0] == \"s\"):\n",
    "                # substitute\n",
    "                final_word.append(path[j][1])\n",
    "                i += 1\n",
    "                j += 1\n",
    "            elif (path[j][0] == \"d\"):\n",
    "                # delete\n",
    "                i += 1\n",
    "                j += 1\n",
    "            else:\n",
    "                # \"i\", insert\n",
    "                final_word.append(path[j][1])\n",
    "                j += 1\n",
    "        else:\n",
    "            if (i < word_len):\n",
    "                final_word.append(word[i])\n",
    "                i += 1\n",
    "            if (j < path_len and i > path[j][2]):\n",
    "                j += 1\n",
    "    return (kb.keyseq_to_word(''.join(final_word)))\n",
    "\n",
    "def path_to_pass(password, idx_path, trans_dict):\n",
    "    '''\n",
    "    This function decodes the password in which the given path transitions the input password into.\n",
    "    Input parameters: original password, transition path, transition dictionary\n",
    "    Output: decoded password\n",
    "    '''\n",
    "#     print(idx_path)\n",
    "    str_path = []\n",
    "    for i in idx_path:\n",
    "        if (i != '<PAD>' and i != '<UNK>') and trans_dict.get(int(i)):\n",
    "            str_path.append(trans_dict[int(i)])\n",
    "#         else:\n",
    "#             print(\"could not find \" + str(i) + \" in dictionary\")\n",
    "#     print(str_path)\n",
    "    output_pass = path2word_kb_feasible(password, str_path)\n",
    "    return output_pass\n",
    "\n",
    "def get_accuracy_beam_decode(logits, pass1_batch, pass2_batch, target_int_to_vocab, trans_dict_2path, bias=False):\n",
    "    \"\"\"\n",
    "    Calculate accuracy of BeamSearch output as follows: if one of the K outputs is correct,\n",
    "    it counts as a correct prediction (positive contribution to the accuracy).\n",
    "    \"logits\" are now of the shape: [batches, max_seq, K]\n",
    "    \"\"\"\n",
    "#     print(logits.shape)\n",
    "    beam_width = logits.shape[2]\n",
    "    match_vec = np.zeros((1, logits.shape[0]), dtype=bool)\n",
    "    for i in range(beam_width):\n",
    "        decode_pred_batch = logits[:,:,i]\n",
    "        for k in range(logits.shape[0]):\n",
    "            decode_pred = logits[k,:,i]\n",
    "#             print(decode_pred)\n",
    "            path_idx_pred = [target_int_to_vocab[j] for j in decode_pred]\n",
    "#             print(path_idx_pred)\n",
    "            if ('<EOS>' in path_idx_pred):\n",
    "                path_idx_pred = path_idx_pred[:path_idx_pred.index('<EOS>')]\n",
    "            prediction = path_to_pass(pass1_batch[k], path_idx_pred, trans_dict_2path)\n",
    "#             print(\"pred: {}, targ: {}\".format(prediction, pass2_batch[k]))\n",
    "            if (prediction == kb.keyseq_to_word(pass2_batch[k])):\n",
    "#                 print(prediction)\n",
    "                match_vec[0, k] = True\n",
    "            if (bias and (not match_vec[0, k]) and (k == logits.shape[0] - 1)):\n",
    "                if (kb.keyseq_to_word(pass2_batch[k]) == kb.keyseq_to_word(pass1_batch[k])):\n",
    "                    match_vec[0, k] = True       \n",
    "    acc = np.mean(match_vec)\n",
    "#     if (max(acc) > 0.6):\n",
    "#         print(target)\n",
    "#         print(np.reshape(np.transpose(logits[:,:,acc.index(max(acc))]), target.shape))\n",
    "    return acc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "print(tf.__version__)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train_pass2path(ds_csv_path, display_step=10, epochs=80, batch_size=50, rnn_size=128, num_layers=3, embed_size=200,\n",
    "                   beam_width=10, edit_distance=3, learning_rate=0.001, keep_probability=0.8):\n",
    "    # Hyperparameters\n",
    "    encoding_embedding_size = embed_size\n",
    "    decoding_embedding_size = embed_size\n",
    "    \n",
    "    # Beam Graph\n",
    "    \n",
    "    trans_dict_2path = IDX_to_TRANS\n",
    "    save_path = 'checkpoints/dev'\n",
    "\n",
    "    train_graph_beam = tf.Graph()\n",
    "    with train_graph_beam.as_default():\n",
    "        input_data, targets, target_sequence_length, max_target_sequence_length = enc_dec_model_inputs()\n",
    "        lr, keep_prob = hyperparam_inputs()\n",
    "\n",
    "        train_logits, inference_logits, infer_lengths = pass2path_model_beam(tf.reverse(input_data, [-1]),\n",
    "                                                              targets,\n",
    "                                                              keep_prob,\n",
    "                                                              batch_size,\n",
    "                                                              target_sequence_length,\n",
    "                                                              max_target_sequence_length,\n",
    "                                                              len(source_vocab_to_int),\n",
    "                                                              len(target_vocab_to_int),\n",
    "                                                              encoding_embedding_size,\n",
    "                                                              decoding_embedding_size,\n",
    "                                                              rnn_size,\n",
    "                                                              num_layers,\n",
    "                                                              target_vocab_to_int,beam_width)\n",
    "\n",
    "        training_logits = tf.identity(train_logits.rnn_output, name='logits')\n",
    "    #     print(inference_logits)\n",
    "        inference_masks = tf.transpose(tf.sequence_mask(infer_lengths,dtype=tf.int32), perm=[0, 2, 1])\n",
    "        inference_logits = tf.identity(inference_logits.predicted_ids * inference_masks, name='predictions')\n",
    "\n",
    "        # https://www.tensorflow.org/api_docs/python/tf/sequence_mask\n",
    "        # - Returns a mask tensor representing the first N positions of each cell.\n",
    "        masks = tf.sequence_mask(target_sequence_length, max_target_sequence_length, dtype=tf.float32, name='masks')\n",
    "\n",
    "        with tf.name_scope(\"optimization\"):\n",
    "            # Loss function - weighted softmax cross entropy\n",
    "            cost = tf.contrib.seq2seq.sequence_loss(\n",
    "                training_logits,\n",
    "                targets,\n",
    "                masks)\n",
    "\n",
    "            # Optimizer\n",
    "            optimizer = tf.train.AdamOptimizer(lr)\n",
    "\n",
    "            # Gradient Clipping\n",
    "            gradients = optimizer.compute_gradients(cost)\n",
    "            capped_gradients = [(tf.clip_by_value(grad, -1., 1.), var) for grad, var in gradients if grad is not None]\n",
    "            train_op = optimizer.apply_gradients(capped_gradients)\n",
    "            \n",
    "    # Beam version v.2\n",
    "    # Locations and loads:\n",
    "    f_path = ds_csv_path\n",
    "    with tf.Session(graph=train_graph_beam) as sess:\n",
    "        sess.run(tf.global_variables_initializer())\n",
    "        num_samples = 0\n",
    "        with open(f_path) as csv_file:\n",
    "            csv_reader = csv.reader(csv_file, delimiter=',')\n",
    "            num_samples = sum(1 for row in csv_reader if len(json.loads(row[2])) <= edit_distance)\n",
    "        total_batches = num_samples//batch_size\n",
    "\n",
    "        print(\"# Samples: {}\".format(num_samples))\n",
    "        print(\"Total batches: {}\".format(total_batches))\n",
    "\n",
    "        # Split data to training and validation sets\n",
    "        num_validation = int(0.2 * num_samples)\n",
    "        total_valid_batches = num_validation//batch_size\n",
    "        total_train_batches = total_batches - total_valid_batches\n",
    "\n",
    "        print(\"Total validation batches: {}\".format(total_valid_batches))\n",
    "        print(\"Total training batches: {}\".format(total_train_batches))\n",
    "\n",
    "        valid_dataset = csv_input_fn_filter(f_path, batch_size, skip_lines = 0,\n",
    "                                            edit_distance = edit_distance, limit = num_validation)\n",
    "        train_dataset = csv_input_fn_filter(f_path, batch_size, skip_lines = num_validation,\n",
    "                                            edit_distance = edit_distance, limit = -1)\n",
    "\n",
    "        valid_iterator = valid_dataset.make_initializable_iterator()\n",
    "        train_iterator = train_dataset.make_initializable_iterator()\n",
    "\n",
    "        next_valid_element = valid_iterator.get_next()\n",
    "        next_train_element = train_iterator.get_next()\n",
    "\n",
    "        sess.run(valid_iterator.initializer)\n",
    "        sess.run(train_iterator.initializer)\n",
    "\n",
    "        start = time.clock()\n",
    "        ckpt_time = start\n",
    "        for epoch_i in range(epochs):\n",
    "            for batch_i in range(total_train_batches):\n",
    "                batch_train = sess.run(next_train_element)\n",
    "                batch_valid = sess.run(next_valid_element)\n",
    "                (source_batch, target_batch, sources_lengths, targets_lengths, pass1_train_batch,\n",
    "                 pass2_train_batch) = preprocess_batch(batch_train, \n",
    "                                                                                                  source_vocab_to_int,\n",
    "                                                                                                  target_vocab_to_int)\n",
    "\n",
    "                (valid_sources_batch, valid_targets_batch, valid_sources_lengths, valid_targets_lengths,\n",
    "                 pass1_valid_batch, pass2_valid_batch) = preprocess_batch(\n",
    "                    batch_valid, \n",
    "                    source_vocab_to_int,\n",
    "                    target_vocab_to_int)\n",
    "\n",
    "                _, loss = sess.run(\n",
    "                    [train_op, cost],\n",
    "                    {input_data: source_batch,\n",
    "                     targets: target_batch,\n",
    "                     lr: learning_rate,\n",
    "                     target_sequence_length: targets_lengths,\n",
    "                     keep_prob: keep_probability})\n",
    "\n",
    "\n",
    "                if batch_i % display_step == 0 and batch_i > 0:\n",
    "                    batch_train_logits = sess.run(\n",
    "                        inference_logits,\n",
    "                        {input_data: source_batch,\n",
    "                         target_sequence_length: targets_lengths,\n",
    "                         keep_prob: 1.0})\n",
    "\n",
    "                    batch_valid_logits = sess.run(\n",
    "                        inference_logits,\n",
    "                        {input_data: valid_sources_batch,\n",
    "                         target_sequence_length: valid_targets_lengths,\n",
    "                         keep_prob: 1.0})\n",
    "    #                 print(batch_train_logits)\n",
    "\n",
    "                    train_acc = get_accuracy_beam(target_batch, batch_train_logits)\n",
    "                    train2_acc = get_accuracy_beam_decode(batch_train_logits, pass1_train_batch, pass2_train_batch,\n",
    "                                                          target_int_to_vocab, trans_dict_2path)\n",
    "                    valid_acc = get_accuracy_beam(valid_targets_batch, batch_valid_logits)\n",
    "                    valid2_acc = get_accuracy_beam_decode(batch_valid_logits, pass1_valid_batch, pass2_valid_batch,\n",
    "                                                          target_int_to_vocab, trans_dict_2path)\n",
    "                    current_time = time.clock()\n",
    "\n",
    "                    print('Time: {:>6.1f}/{:>6.1f} Epoch {:>3} Batch {:>4}/{} - Train Accuracy: {:>6.4f}, Validation Accuracy: {:>6.4f}, Loss: {:>6.4f}'\n",
    "                          .format(current_time - ckpt_time ,current_time - start, epoch_i, batch_i, total_train_batches,\n",
    "                                  train2_acc, valid2_acc, loss))\n",
    "                    # Checkpoint:\n",
    "                    saver = tf.train.Saver()\n",
    "                    saver.save(sess, save_path)\n",
    "                    save_params(save_path)\n",
    "                    ckpt_time = current_time\n",
    "\n",
    "        # Save Model\n",
    "        saver = tf.train.Saver()\n",
    "        saver.save(sess, save_path)\n",
    "        print('Model Trained and Saved')\n",
    "        # Save parameters for checkpoint\n",
    "        save_params(save_path)\n",
    "        print('Path to parameters saved')\n",
    "        print('Total time: {:>6.1f}'.format(time.clock() - start))\n",
    "        \n",
    "def predict_pass2path(password, batch_size=50, beam_width=10):\n",
    "    trans_dict_2path = IDX_to_TRANS\n",
    "    load_path = load_params()\n",
    "\n",
    "    original_pass = password\n",
    "\n",
    "    decoded_origin_pass = pass_to_seq(original_pass, source_vocab_to_int)\n",
    "#     print(type([decoded_origin_pass]*batch_size))\n",
    "\n",
    "    loaded_graph = tf.Graph()\n",
    "    with tf.Session(graph=loaded_graph) as sess:\n",
    "        # Load saved model\n",
    "        loader = tf.train.import_meta_graph(load_path + '.meta')\n",
    "        loader.restore(sess, load_path)\n",
    "\n",
    "        input_data = loaded_graph.get_tensor_by_name('input:0')\n",
    "        logits = loaded_graph.get_tensor_by_name('predictions:0')\n",
    "        target_sequence_length = loaded_graph.get_tensor_by_name('target_sequence_length:0')\n",
    "        keep_prob = loaded_graph.get_tensor_by_name('keep_prob:0')\n",
    "\n",
    "        decode_logits = sess.run(logits, {input_data: [decoded_origin_pass]*batch_size,\n",
    "                                             target_sequence_length: [len(decoded_origin_pass)*2]*batch_size,\n",
    "                                             keep_prob: 1.0})[0]\n",
    "    #     print(decode_logits)\n",
    "        print('Input')\n",
    "        print('  Password Ids:      {}'.format([i for i in decoded_origin_pass]))\n",
    "        print('  Password Characters: {}'.format([source_int_to_vocab[i] for i in decoded_origin_pass]))\n",
    "\n",
    "        decode_logits = np.array(decode_logits)\n",
    "        print('Predictions')\n",
    "        for i in range(beam_width):\n",
    "            decode_pred = decode_logits[:,i]\n",
    "            path_idx_pred = [target_int_to_vocab[j] for j in decode_pred]\n",
    "            if ('<EOS>' in path_idx_pred):\n",
    "                path_idx_pred = path_idx_pred[:path_idx_pred.index('<EOS>')]\n",
    "            print('--------------')\n",
    "            print('  Path Ids:      {}'.format([i for i in decode_pred]))\n",
    "            print('  Path Componenets: {}'.format(\" \".join([target_int_to_vocab[i] for i in decode_pred])))\n",
    "            print('  Decoded Password: {}'.format(path_to_pass(original_pass, path_idx_pred, trans_dict_2path)))\n",
    "            print('--------------')\n",
    "            \n",
    "def predict_list_pass2path(password, batch_size=50, beam_width=10):      \n",
    "\n",
    "    trans_dict_2path = IDX_to_TRANS\n",
    "    load_path = load_params()\n",
    "\n",
    "    original_pass = password\n",
    "\n",
    "    decoded_origin_pass = pass_to_seq(original_pass, source_vocab_to_int)\n",
    "    \n",
    "    \n",
    "    predictions = []\n",
    "\n",
    "    loaded_graph = tf.Graph()\n",
    "    with tf.Session(graph=loaded_graph) as sess:\n",
    "        # Load saved model\n",
    "        loader = tf.train.import_meta_graph(load_path + '.meta')\n",
    "        loader.restore(sess, load_path)\n",
    "\n",
    "        input_data = loaded_graph.get_tensor_by_name('input:0')\n",
    "        logits = loaded_graph.get_tensor_by_name('predictions:0')\n",
    "        target_sequence_length = loaded_graph.get_tensor_by_name('target_sequence_length:0')\n",
    "        keep_prob = loaded_graph.get_tensor_by_name('keep_prob:0')\n",
    "\n",
    "        decode_logits = sess.run(logits, {input_data: [decoded_origin_pass]*batch_size,\n",
    "                                             target_sequence_length: [len(decoded_origin_pass)*2]*batch_size,\n",
    "                                             keep_prob: 1.0})[0]\n",
    "        decode_logits = np.array(decode_logits)\n",
    "        for i in range(beam_width):\n",
    "            decode_pred = decode_logits[:,i]\n",
    "            path_idx_pred = [target_int_to_vocab[j] for j in decode_pred]\n",
    "            if ('<EOS>' in path_idx_pred):\n",
    "                path_idx_pred = path_idx_pred[:path_idx_pred.index('<EOS>')]\n",
    "            predictions.append(path_to_pass(original_pass, path_idx_pred, trans_dict_2path))\n",
    "    return predictions"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 57,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "# Samples: 1156\n",
      "Total batches: 38\n",
      "Total validation batches: 7\n",
      "Total training batches: 31\n",
      "Time:    3.1/   3.1 Epoch   0 Batch   30/31 - Train Accuracy: 0.0333, Validation Accuracy: 0.0667, Loss: 4.4452\n",
      "Time:    2.7/   5.8 Epoch   1 Batch   30/31 - Train Accuracy: 0.1333, Validation Accuracy: 0.1333, Loss: 3.2561\n",
      "Time:    2.6/   8.3 Epoch   2 Batch   30/31 - Train Accuracy: 0.2000, Validation Accuracy: 0.1000, Loss: 3.4136\n",
      "Time:    2.6/  10.9 Epoch   3 Batch   30/31 - Train Accuracy: 0.0667, Validation Accuracy: 0.0667, Loss: 3.5686\n",
      "Time:    2.7/  13.6 Epoch   4 Batch   30/31 - Train Accuracy: 0.1667, Validation Accuracy: 0.1333, Loss: 3.1221\n",
      "Time:    2.3/  15.9 Epoch   5 Batch   30/31 - Train Accuracy: 0.1333, Validation Accuracy: 0.1333, Loss: 3.5139\n",
      "Time:    2.4/  18.3 Epoch   6 Batch   30/31 - Train Accuracy: 0.1667, Validation Accuracy: 0.1333, Loss: 2.9482\n",
      "Time:    2.2/  20.5 Epoch   7 Batch   30/31 - Train Accuracy: 0.3000, Validation Accuracy: 0.2000, Loss: 2.6294\n",
      "Time:    2.4/  22.9 Epoch   8 Batch   30/31 - Train Accuracy: 0.3000, Validation Accuracy: 0.2667, Loss: 2.8859\n",
      "Time:    2.5/  25.5 Epoch   9 Batch   30/31 - Train Accuracy: 0.1667, Validation Accuracy: 0.1000, Loss: 2.7422\n",
      "Time:    2.5/  27.9 Epoch  10 Batch   30/31 - Train Accuracy: 0.3333, Validation Accuracy: 0.1667, Loss: 3.0746\n",
      "Time:    2.5/  30.4 Epoch  11 Batch   30/31 - Train Accuracy: 0.3667, Validation Accuracy: 0.1667, Loss: 2.3614\n",
      "Time:    2.5/  32.8 Epoch  12 Batch   30/31 - Train Accuracy: 0.1333, Validation Accuracy: 0.1333, Loss: 2.7142\n",
      "Time:    2.7/  35.5 Epoch  13 Batch   30/31 - Train Accuracy: 0.2667, Validation Accuracy: 0.2667, Loss: 2.1992\n",
      "Time:    2.4/  38.0 Epoch  14 Batch   30/31 - Train Accuracy: 0.2667, Validation Accuracy: 0.0667, Loss: 2.3389\n",
      "Time:    2.3/  40.3 Epoch  15 Batch   30/31 - Train Accuracy: 0.3333, Validation Accuracy: 0.3000, Loss: 2.4802\n",
      "Time:    2.6/  42.9 Epoch  16 Batch   30/31 - Train Accuracy: 0.3000, Validation Accuracy: 0.0667, Loss: 2.0819\n",
      "Time:    2.6/  45.5 Epoch  17 Batch   30/31 - Train Accuracy: 0.2667, Validation Accuracy: 0.2000, Loss: 2.1548\n",
      "Time:    2.6/  48.1 Epoch  18 Batch   30/31 - Train Accuracy: 0.2000, Validation Accuracy: 0.1000, Loss: 2.5763\n",
      "Time:    2.6/  50.7 Epoch  19 Batch   30/31 - Train Accuracy: 0.1333, Validation Accuracy: 0.1667, Loss: 2.1266\n",
      "Time:    2.7/  53.3 Epoch  20 Batch   30/31 - Train Accuracy: 0.2333, Validation Accuracy: 0.1333, Loss: 2.0879\n",
      "Time:    2.6/  55.9 Epoch  21 Batch   30/31 - Train Accuracy: 0.2000, Validation Accuracy: 0.2000, Loss: 1.9696\n",
      "Time:    2.4/  58.3 Epoch  22 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.2333, Loss: 1.8067\n",
      "Time:    2.7/  61.0 Epoch  23 Batch   30/31 - Train Accuracy: 0.3000, Validation Accuracy: 0.1333, Loss: 1.9126\n",
      "Time:    2.6/  63.6 Epoch  24 Batch   30/31 - Train Accuracy: 0.3667, Validation Accuracy: 0.2333, Loss: 2.0545\n",
      "Time:    2.6/  66.2 Epoch  25 Batch   30/31 - Train Accuracy: 0.3333, Validation Accuracy: 0.2667, Loss: 2.0671\n",
      "Time:    2.6/  68.8 Epoch  26 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.3333, Loss: 2.0170\n",
      "Time:    2.7/  71.4 Epoch  27 Batch   30/31 - Train Accuracy: 0.3333, Validation Accuracy: 0.1667, Loss: 1.8436\n",
      "Time:    2.7/  74.1 Epoch  28 Batch   30/31 - Train Accuracy: 0.2333, Validation Accuracy: 0.2000, Loss: 2.2545\n",
      "Time:    2.6/  76.8 Epoch  29 Batch   30/31 - Train Accuracy: 0.3667, Validation Accuracy: 0.1333, Loss: 2.1601\n",
      "Time:    2.4/  79.1 Epoch  30 Batch   30/31 - Train Accuracy: 0.3333, Validation Accuracy: 0.2333, Loss: 2.1814\n",
      "Time:    2.7/  81.8 Epoch  31 Batch   30/31 - Train Accuracy: 0.1667, Validation Accuracy: 0.3000, Loss: 2.1140\n",
      "Time:    2.7/  84.6 Epoch  32 Batch   30/31 - Train Accuracy: 0.2333, Validation Accuracy: 0.2333, Loss: 2.3884\n",
      "Time:    2.8/  87.4 Epoch  33 Batch   30/31 - Train Accuracy: 0.3000, Validation Accuracy: 0.3000, Loss: 1.8711\n",
      "Time:    2.8/  90.2 Epoch  34 Batch   30/31 - Train Accuracy: 0.2667, Validation Accuracy: 0.3000, Loss: 1.9359\n",
      "Time:    2.8/  93.0 Epoch  35 Batch   30/31 - Train Accuracy: 0.4000, Validation Accuracy: 0.4000, Loss: 1.5851\n",
      "Time:    2.9/  95.9 Epoch  36 Batch   30/31 - Train Accuracy: 0.2333, Validation Accuracy: 0.2333, Loss: 1.9885\n",
      "Time:    2.8/  98.7 Epoch  37 Batch   30/31 - Train Accuracy: 0.2333, Validation Accuracy: 0.3000, Loss: 1.8302\n",
      "Time:    2.6/ 101.3 Epoch  38 Batch   30/31 - Train Accuracy: 0.2667, Validation Accuracy: 0.2333, Loss: 1.7597\n",
      "Time:    2.8/ 104.1 Epoch  39 Batch   30/31 - Train Accuracy: 0.3000, Validation Accuracy: 0.2000, Loss: 1.6155\n",
      "Time:    2.9/ 107.0 Epoch  40 Batch   30/31 - Train Accuracy: 0.2333, Validation Accuracy: 0.3000, Loss: 2.1218\n",
      "Time:    2.8/ 109.8 Epoch  41 Batch   30/31 - Train Accuracy: 0.2333, Validation Accuracy: 0.3000, Loss: 1.8385\n",
      "Time:    2.9/ 112.7 Epoch  42 Batch   30/31 - Train Accuracy: 0.3000, Validation Accuracy: 0.2667, Loss: 1.7136\n",
      "Time:    2.8/ 115.5 Epoch  43 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.3667, Loss: 1.4273\n",
      "Time:    3.0/ 118.5 Epoch  44 Batch   30/31 - Train Accuracy: 0.3667, Validation Accuracy: 0.3333, Loss: 1.6468\n",
      "Time:    2.7/ 121.1 Epoch  45 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.2333, Loss: 1.6444\n",
      "Time:    2.9/ 124.0 Epoch  46 Batch   30/31 - Train Accuracy: 0.2667, Validation Accuracy: 0.3000, Loss: 1.8498\n",
      "Time:    3.2/ 127.2 Epoch  47 Batch   30/31 - Train Accuracy: 0.4000, Validation Accuracy: 0.1333, Loss: 1.5973\n",
      "Time:    3.1/ 130.3 Epoch  48 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.3000, Loss: 1.5768\n",
      "Time:    3.2/ 133.5 Epoch  49 Batch   30/31 - Train Accuracy: 0.2667, Validation Accuracy: 0.1333, Loss: 1.7426\n",
      "Time:    3.2/ 136.7 Epoch  50 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.3000, Loss: 1.5378\n",
      "Time:    3.3/ 140.1 Epoch  51 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.1667, Loss: 1.5757\n",
      "Time:    3.5/ 143.6 Epoch  52 Batch   30/31 - Train Accuracy: 0.2333, Validation Accuracy: 0.4667, Loss: 1.7994\n",
      "Time:    3.2/ 146.8 Epoch  53 Batch   30/31 - Train Accuracy: 0.2333, Validation Accuracy: 0.2667, Loss: 1.9124\n",
      "Time:    4.1/ 150.9 Epoch  54 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.1333, Loss: 1.5804\n",
      "Time:    4.1/ 155.1 Epoch  55 Batch   30/31 - Train Accuracy: 0.4000, Validation Accuracy: 0.3667, Loss: 1.7962\n",
      "Time:    5.1/ 160.2 Epoch  56 Batch   30/31 - Train Accuracy: 0.3333, Validation Accuracy: 0.2333, Loss: 1.6302\n",
      "Time:    7.4/ 167.6 Epoch  57 Batch   30/31 - Train Accuracy: 0.2667, Validation Accuracy: 0.1667, Loss: 1.8674\n",
      "Time:    8.1/ 175.7 Epoch  58 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.4000, Loss: 1.3806\n",
      "Time:    8.3/ 183.9 Epoch  59 Batch   30/31 - Train Accuracy: 0.3000, Validation Accuracy: 0.2667, Loss: 1.7368\n",
      "Time:    7.7/ 191.7 Epoch  60 Batch   30/31 - Train Accuracy: 0.3333, Validation Accuracy: 0.2333, Loss: 1.4265\n",
      "Time:    6.9/ 198.5 Epoch  61 Batch   30/31 - Train Accuracy: 0.3333, Validation Accuracy: 0.3000, Loss: 1.7445\n",
      "Time:    7.4/ 205.9 Epoch  62 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.2000, Loss: 1.5194\n",
      "Time:    5.7/ 211.7 Epoch  63 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.4667, Loss: 1.5011\n",
      "Time:    4.5/ 216.2 Epoch  64 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.2000, Loss: 1.4911\n",
      "Time:    3.9/ 220.1 Epoch  65 Batch   30/31 - Train Accuracy: 0.5667, Validation Accuracy: 0.2333, Loss: 1.3860\n",
      "Time:    3.8/ 223.9 Epoch  66 Batch   30/31 - Train Accuracy: 0.3000, Validation Accuracy: 0.2667, Loss: 1.6832\n",
      "Time:    3.5/ 227.4 Epoch  67 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.2333, Loss: 1.3199\n",
      "Time:    3.1/ 230.5 Epoch  68 Batch   30/31 - Train Accuracy: 0.3667, Validation Accuracy: 0.1333, Loss: 1.4976\n",
      "Time:    3.4/ 233.9 Epoch  69 Batch   30/31 - Train Accuracy: 0.3667, Validation Accuracy: 0.2000, Loss: 1.3359\n",
      "Time:    3.4/ 237.3 Epoch  70 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.4333, Loss: 1.3797\n",
      "Time:    3.7/ 241.0 Epoch  71 Batch   30/31 - Train Accuracy: 0.4000, Validation Accuracy: 0.3333, Loss: 1.3752\n",
      "Time:    3.7/ 244.7 Epoch  72 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.0667, Loss: 1.3434\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Time:    3.7/ 248.4 Epoch  73 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.1333, Loss: 1.4348\n",
      "Time:    4.0/ 252.4 Epoch  74 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.3000, Loss: 1.3427\n",
      "Time:    4.3/ 256.7 Epoch  75 Batch   30/31 - Train Accuracy: 0.2667, Validation Accuracy: 0.4000, Loss: 1.3637\n",
      "Time:    4.1/ 260.8 Epoch  76 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.2667, Loss: 1.4781\n",
      "Time:    5.7/ 266.5 Epoch  77 Batch   30/31 - Train Accuracy: 0.4000, Validation Accuracy: 0.2333, Loss: 1.3718\n",
      "Time:    9.7/ 276.1 Epoch  78 Batch   30/31 - Train Accuracy: 0.3667, Validation Accuracy: 0.2667, Loss: 1.3538\n",
      "Time:    6.7/ 282.9 Epoch  79 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.1333, Loss: 1.3088\n",
      "Time:    4.9/ 287.8 Epoch  80 Batch   30/31 - Train Accuracy: 0.4000, Validation Accuracy: 0.1667, Loss: 1.3947\n",
      "Time:    4.2/ 292.0 Epoch  81 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.2000, Loss: 1.2464\n",
      "Time:    3.8/ 295.8 Epoch  82 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.1333, Loss: 1.1756\n",
      "Time:    3.6/ 299.4 Epoch  83 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.2333, Loss: 1.3288\n",
      "Time:    3.3/ 302.7 Epoch  84 Batch   30/31 - Train Accuracy: 0.3667, Validation Accuracy: 0.2333, Loss: 1.3141\n",
      "Time:    3.6/ 306.3 Epoch  85 Batch   30/31 - Train Accuracy: 0.4333, Validation Accuracy: 0.1333, Loss: 1.3684\n",
      "Time:    3.5/ 309.8 Epoch  86 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.3000, Loss: 1.2110\n",
      "Time:    3.6/ 313.5 Epoch  87 Batch   30/31 - Train Accuracy: 0.4000, Validation Accuracy: 0.2667, Loss: 1.2225\n",
      "Time:    3.9/ 317.3 Epoch  88 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.2333, Loss: 1.1671\n",
      "Time:    4.3/ 321.6 Epoch  89 Batch   30/31 - Train Accuracy: 0.6000, Validation Accuracy: 0.1667, Loss: 1.0418\n",
      "Time:    4.8/ 326.4 Epoch  90 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.2000, Loss: 1.3596\n",
      "Time:    5.5/ 331.9 Epoch  91 Batch   30/31 - Train Accuracy: 0.2333, Validation Accuracy: 0.2333, Loss: 1.4705\n",
      "Time:    8.2/ 340.1 Epoch  92 Batch   30/31 - Train Accuracy: 0.4000, Validation Accuracy: 0.1333, Loss: 1.2376\n",
      "Time:    5.2/ 345.3 Epoch  93 Batch   30/31 - Train Accuracy: 0.5667, Validation Accuracy: 0.2333, Loss: 1.1228\n",
      "Time:    4.5/ 349.8 Epoch  94 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.2667, Loss: 1.1867\n",
      "Time:    4.2/ 354.0 Epoch  95 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.2667, Loss: 1.2340\n",
      "Time:    3.8/ 357.8 Epoch  96 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.2667, Loss: 1.0657\n",
      "Time:    3.7/ 361.5 Epoch  97 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.4000, Loss: 1.1831\n",
      "Time:    3.5/ 365.1 Epoch  98 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.2667, Loss: 1.2068\n",
      "Time:    3.3/ 368.3 Epoch  99 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.3667, Loss: 1.1790\n",
      "Time:    3.7/ 372.0 Epoch 100 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.1667, Loss: 1.1651\n",
      "Time:    3.8/ 375.8 Epoch 101 Batch   30/31 - Train Accuracy: 0.5333, Validation Accuracy: 0.3000, Loss: 1.2148\n",
      "Time:    4.1/ 379.9 Epoch 102 Batch   30/31 - Train Accuracy: 0.5333, Validation Accuracy: 0.1000, Loss: 1.1874\n",
      "Time:    4.6/ 384.5 Epoch 103 Batch   30/31 - Train Accuracy: 0.5667, Validation Accuracy: 0.3000, Loss: 1.1859\n",
      "Time:    4.8/ 389.4 Epoch 104 Batch   30/31 - Train Accuracy: 0.5333, Validation Accuracy: 0.2333, Loss: 1.0570\n",
      "Time:    6.4/ 395.8 Epoch 105 Batch   30/31 - Train Accuracy: 0.6667, Validation Accuracy: 0.2667, Loss: 1.1274\n",
      "Time:    7.8/ 403.5 Epoch 106 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.1667, Loss: 1.2049\n",
      "Time:    6.6/ 410.2 Epoch 107 Batch   30/31 - Train Accuracy: 0.7000, Validation Accuracy: 0.2667, Loss: 1.0029\n",
      "Time:    4.9/ 415.1 Epoch 108 Batch   30/31 - Train Accuracy: 0.5667, Validation Accuracy: 0.2333, Loss: 1.2019\n",
      "Time:    4.4/ 419.4 Epoch 109 Batch   30/31 - Train Accuracy: 0.8000, Validation Accuracy: 0.1667, Loss: 0.9118\n",
      "Time:    4.0/ 423.4 Epoch 110 Batch   30/31 - Train Accuracy: 0.6333, Validation Accuracy: 0.2000, Loss: 1.0169\n",
      "Time:    3.7/ 427.2 Epoch 111 Batch   30/31 - Train Accuracy: 0.7000, Validation Accuracy: 0.2667, Loss: 1.0286\n",
      "Time:    3.7/ 430.9 Epoch 112 Batch   30/31 - Train Accuracy: 0.4667, Validation Accuracy: 0.3667, Loss: 1.0440\n",
      "Time:    3.7/ 434.5 Epoch 113 Batch   30/31 - Train Accuracy: 0.6667, Validation Accuracy: 0.2333, Loss: 0.9767\n",
      "Time:    3.3/ 437.8 Epoch 114 Batch   30/31 - Train Accuracy: 0.3667, Validation Accuracy: 0.1333, Loss: 1.1850\n",
      "Time:    3.7/ 441.5 Epoch 115 Batch   30/31 - Train Accuracy: 0.6000, Validation Accuracy: 0.1667, Loss: 1.1594\n",
      "Time:    3.9/ 445.5 Epoch 116 Batch   30/31 - Train Accuracy: 0.6667, Validation Accuracy: 0.2000, Loss: 0.9985\n",
      "Time:    4.4/ 449.9 Epoch 117 Batch   30/31 - Train Accuracy: 0.7333, Validation Accuracy: 0.3333, Loss: 1.0322\n",
      "Time:    5.0/ 454.9 Epoch 118 Batch   30/31 - Train Accuracy: 0.6000, Validation Accuracy: 0.2000, Loss: 1.1422\n",
      "Time:    6.9/ 461.8 Epoch 119 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.2000, Loss: 0.9082\n",
      "Time:    9.2/ 471.0 Epoch 120 Batch   30/31 - Train Accuracy: 0.6667, Validation Accuracy: 0.2000, Loss: 0.9912\n",
      "Time:    6.6/ 477.6 Epoch 121 Batch   30/31 - Train Accuracy: 0.6333, Validation Accuracy: 0.1000, Loss: 0.9767\n",
      "Time:    4.7/ 482.3 Epoch 122 Batch   30/31 - Train Accuracy: 0.6667, Validation Accuracy: 0.0667, Loss: 0.9229\n",
      "Time:    4.2/ 486.5 Epoch 123 Batch   30/31 - Train Accuracy: 0.6000, Validation Accuracy: 0.1000, Loss: 1.0080\n",
      "Time:    4.1/ 490.6 Epoch 124 Batch   30/31 - Train Accuracy: 0.8000, Validation Accuracy: 0.1333, Loss: 0.8488\n",
      "Time:    3.9/ 494.4 Epoch 125 Batch   30/31 - Train Accuracy: 0.6667, Validation Accuracy: 0.0667, Loss: 0.9206\n",
      "Time:    3.9/ 498.3 Epoch 126 Batch   30/31 - Train Accuracy: 0.7333, Validation Accuracy: 0.1667, Loss: 0.9743\n",
      "Time:    3.9/ 502.2 Epoch 127 Batch   30/31 - Train Accuracy: 0.7000, Validation Accuracy: 0.3333, Loss: 1.0230\n",
      "Time:    3.7/ 505.9 Epoch 128 Batch   30/31 - Train Accuracy: 0.7000, Validation Accuracy: 0.1333, Loss: 0.9169\n",
      "Time:    3.5/ 509.4 Epoch 129 Batch   30/31 - Train Accuracy: 0.5667, Validation Accuracy: 0.1333, Loss: 1.0662\n",
      "Time:    4.2/ 513.6 Epoch 130 Batch   30/31 - Train Accuracy: 0.5000, Validation Accuracy: 0.2000, Loss: 1.0753\n",
      "Time:    4.4/ 518.0 Epoch 131 Batch   30/31 - Train Accuracy: 0.7333, Validation Accuracy: 0.2000, Loss: 0.9861\n",
      "Time:    5.0/ 523.0 Epoch 132 Batch   30/31 - Train Accuracy: 0.8667, Validation Accuracy: 0.1333, Loss: 0.7820\n",
      "Time:    6.3/ 529.3 Epoch 133 Batch   30/31 - Train Accuracy: 0.7333, Validation Accuracy: 0.2333, Loss: 0.9604\n",
      "Time:    8.7/ 538.0 Epoch 134 Batch   30/31 - Train Accuracy: 0.7000, Validation Accuracy: 0.0667, Loss: 0.8701\n",
      "Time:    9.0/ 547.0 Epoch 135 Batch   30/31 - Train Accuracy: 0.7333, Validation Accuracy: 0.1000, Loss: 0.8744\n",
      "Time:    5.7/ 552.8 Epoch 136 Batch   30/31 - Train Accuracy: 0.8333, Validation Accuracy: 0.1333, Loss: 0.8790\n",
      "Time:    4.3/ 557.1 Epoch 137 Batch   30/31 - Train Accuracy: 0.7667, Validation Accuracy: 0.3667, Loss: 0.8906\n",
      "Time:    4.6/ 561.7 Epoch 138 Batch   30/31 - Train Accuracy: 0.6667, Validation Accuracy: 0.2333, Loss: 1.0077\n",
      "Time:    4.2/ 565.9 Epoch 139 Batch   30/31 - Train Accuracy: 0.7000, Validation Accuracy: 0.3000, Loss: 0.9488\n",
      "Time:    3.8/ 569.7 Epoch 140 Batch   30/31 - Train Accuracy: 0.7667, Validation Accuracy: 0.1333, Loss: 0.8774\n",
      "Time:    4.0/ 573.7 Epoch 141 Batch   30/31 - Train Accuracy: 0.8667, Validation Accuracy: 0.1667, Loss: 0.8228\n",
      "Time:    3.8/ 577.5 Epoch 142 Batch   30/31 - Train Accuracy: 0.8000, Validation Accuracy: 0.2000, Loss: 0.8015\n",
      "Time:    3.8/ 581.4 Epoch 143 Batch   30/31 - Train Accuracy: 0.7000, Validation Accuracy: 0.2333, Loss: 0.9459\n",
      "Time:    3.9/ 585.2 Epoch 144 Batch   30/31 - Train Accuracy: 0.7000, Validation Accuracy: 0.1667, Loss: 0.8998\n",
      "Time:    4.0/ 589.3 Epoch 145 Batch   30/31 - Train Accuracy: 0.7667, Validation Accuracy: 0.2000, Loss: 0.8819\n",
      "Time:    4.3/ 593.6 Epoch 146 Batch   30/31 - Train Accuracy: 0.8333, Validation Accuracy: 0.0667, Loss: 0.8199\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Time:    4.5/ 598.1 Epoch 147 Batch   30/31 - Train Accuracy: 0.8000, Validation Accuracy: 0.0667, Loss: 0.7881\n",
      "Time:    4.7/ 602.9 Epoch 148 Batch   30/31 - Train Accuracy: 0.8333, Validation Accuracy: 0.1333, Loss: 0.8990\n",
      "Time:    5.7/ 608.6 Epoch 149 Batch   30/31 - Train Accuracy: 0.7000, Validation Accuracy: 0.2000, Loss: 0.8786\n",
      "Model Trained and Saved\n",
      "Path to parameters saved\n",
      "Total time:  613.1\n"
     ]
    }
   ],
   "source": [
    "f_path = 'cleaned_pw_paths_tr_10000.csv'\n",
    "train_pass2path(ds_csv_path = f_path, display_step=30, epochs=150, batch_size=30, rnn_size=128, num_layers=3, embed_size=200,\n",
    "                   beam_width=10, edit_distance=3, learning_rate=0.01, keep_probability=0.6)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Restoring parameters from checkpoints/dev\n",
      "Input\n",
      "  Password Ids:      [84, 10, 50, 72, 89, 79, 63, 97]\n",
      "  Password Characters: ['g', 'a', 'm', 'e', 'b', 'o', 'y', '5']\n",
      "Predictions\n",
      "--------------\n",
      "  Path Ids:      [11899, 1, 0, 0]\n",
      "  Path Componenets: 11895 <EOS> <PAD> <PAD>\n",
      "  Decoded Password: Gameboy5\n",
      "--------------\n",
      "--------------\n",
      "  Path Ids:      [11960, 1, 0, 0]\n",
      "  Path Componenets: 11956 <EOS> <PAD> <PAD>\n",
      "  Decoded Password: GAMEBOY5\n",
      "--------------\n",
      "--------------\n",
      "  Path Ids:      [11, 1, 0, 0]\n",
      "  Path Componenets: 7 <EOS> <PAD> <PAD>\n",
      "  Decoded Password: gameboy\n",
      "--------------\n",
      "--------------\n",
      "  Path Ids:      [11235, 1, 0, 0]\n",
      "  Path Componenets: 11231 <EOS> <PAD> <PAD>\n",
      "  Decoded Password: gameboy15\n",
      "--------------\n",
      "--------------\n",
      "  Path Ids:      [12, 1, 0, 0]\n",
      "  Path Componenets: 8 <EOS> <PAD> <PAD>\n",
      "  Decoded Password: gameboy5\n",
      "--------------\n",
      "--------------\n",
      "  Path Ids:      [11899, 11, 12, 1]\n",
      "  Path Componenets: 11895 7 8 <EOS>\n",
      "  Decoded Password: Gameboy\n",
      "--------------\n",
      "--------------\n",
      "  Path Ids:      [6410, 11, 12, 1]\n",
      "  Path Componenets: 6406 7 8 <EOS>\n",
      "  Decoded Password: ggameboy\n",
      "--------------\n",
      "--------------\n",
      "  Path Ids:      [11960, 12, 13, 1]\n",
      "  Path Componenets: 11956 8 9 <EOS>\n",
      "  Decoded Password: GAMEBOY5\n",
      "--------------\n",
      "--------------\n",
      "  Path Ids:      [10, 11, 1, 0]\n",
      "  Path Componenets: 6 7 <EOS> <PAD>\n",
      "  Decoded Password: gamebo\n",
      "--------------\n",
      "--------------\n",
      "  Path Ids:      [11899, 12, 1, 0]\n",
      "  Path Componenets: 11895 8 <EOS> <PAD>\n",
      "  Decoded Password: Gameboy5\n",
      "--------------\n"
     ]
    }
   ],
   "source": [
    "predict_pass2path('gameboy5', batch_size=30, beam_width=10)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 59,
   "metadata": {},
   "outputs": [],
   "source": [
    "def test_samples_gen(fpath):\n",
    "    with open(fpath) as tst_file:\n",
    "        for line in tst_file:\n",
    "            p = line.split('\\t')\n",
    "            yield p[0], p[1][:-2]\n",
    "def test_batches_gen(fpath, batch_size):\n",
    "    samples_gen = test_samples_gen(fpath)\n",
    "    curr_batch_size = 0\n",
    "    source_batch = []\n",
    "    target_batch = []\n",
    "    for i, sample in enumerate(samples_gen):\n",
    "        if (curr_batch_size < batch_size):\n",
    "            source_batch.append(sample[0])\n",
    "            target_batch.append(sample[1])\n",
    "            curr_batch_size += 1\n",
    "        else:\n",
    "            yield source_batch, target_batch\n",
    "            curr_batch_size = 0\n",
    "            source_batch = []\n",
    "            target_batch = []\n",
    "    yield source_batch, target_batch\n",
    "    \n",
    "def preprocess_batch_prediction(batch, source_vocab_to_int):\n",
    "    pass_batch = [kb.word_to_keyseq(d) for d in batch[0]]\n",
    "    pass2_batch = [kb.word_to_keyseq(d) for d in batch[1]]\n",
    "\n",
    "    pass_batch_ids = [pass_to_seq(p, source_vocab_to_int) for p in pass_batch]\n",
    "    source_pad_int = source_vocab_to_int['<PAD>']\n",
    "    # Pad\n",
    "    pad_sources_batch = np.array(pad_sequence_batch(pass_batch_ids, source_pad_int))\n",
    "    return (pad_sources_batch, pass_batch, pass2_batch)\n",
    "\n",
    "def path2pass_run_testset(fpath, batch_size=50):\n",
    "    '''\n",
    "    This function loads a pre-trained pass2path model and outputs the accuracy of the model\n",
    "    on the input test set\n",
    "    '''\n",
    "\n",
    "    trans_dict_2path = IDX_to_TRANS\n",
    "    load_path = load_params()\n",
    "    \n",
    "    loaded_graph = tf.Graph()\n",
    "    with tf.Session(graph=loaded_graph) as sess:\n",
    "        # Load saved model\n",
    "        loader = tf.train.import_meta_graph(load_path + '.meta')\n",
    "        loader.restore(sess, load_path)\n",
    "\n",
    "        input_data = loaded_graph.get_tensor_by_name('input:0')\n",
    "        logits = loaded_graph.get_tensor_by_name('predictions:0')\n",
    "        target_sequence_length = loaded_graph.get_tensor_by_name('target_sequence_length:0')\n",
    "        keep_prob = loaded_graph.get_tensor_by_name('keep_prob:0')\n",
    "        \n",
    "        acc = 0\n",
    "        b_gen = test_batches_gen(fpath, batch_size)\n",
    "        total_samples = 0\n",
    "        \n",
    "        for batch_i, batch in enumerate(b_gen):\n",
    "            total_samples += len(batch[0])\n",
    "#             print(max_seq_len)\n",
    "            if len(batch[0]) < batch_size:\n",
    "#                 print(s_batch)\n",
    "                valid_size = len(batch[0])\n",
    "                # this is the last batch\n",
    "                while (len(batch[0]) < batch_size):\n",
    "                    batch[0].append('0')\n",
    "                    batch[1].append('0')\n",
    "#                 print(batch)\n",
    "                pad_encoded_s_batch, s_batch, t_batch = preprocess_batch_prediction(batch, source_vocab_to_int)\n",
    "                max_seq_len = max([len(s) for s in pad_encoded_s_batch])\n",
    "                decode_logits = sess.run(logits, {input_data: pad_encoded_s_batch,\n",
    "                                                  target_sequence_length: [max_seq_len*2]*batch_size,\n",
    "                                                  keep_prob: 1.0})\n",
    "                decode_logits = decode_logits[:valid_size,:,:]\n",
    "                b_acc = get_accuracy_beam_decode(decode_logits, s_batch[:valid_size], t_batch[:valid_size],\n",
    "                                                 target_int_to_vocab, trans_dict_2path, bias=True)\n",
    "                acc += valid_size * b_acc\n",
    "            else:\n",
    "                pad_encoded_s_batch, s_batch, t_batch = preprocess_batch_prediction(batch, source_vocab_to_int)\n",
    "                max_seq_len = max([len(s) for s in pad_encoded_s_batch])\n",
    "                decode_logits = sess.run(logits, {input_data: pad_encoded_s_batch,\n",
    "                                                  target_sequence_length: [max_seq_len*2]*batch_size,\n",
    "                                                  keep_prob: 1.0})\n",
    "                b_acc = get_accuracy_beam_decode(decode_logits, s_batch, t_batch,\n",
    "                                                 target_int_to_vocab, trans_dict_2path, bias=True)\n",
    "                acc += batch_size * b_acc\n",
    "        acc = acc / total_samples\n",
    "        return acc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "INFO:tensorflow:Restoring parameters from checkpoints/dev\n",
      "0.3307766059443912\n"
     ]
    }
   ],
   "source": [
    "fpath = 'edited_tst_file.txt'\n",
    "acc = path2pass_run_testset(fpath, batch_size=30)\n",
    "print(acc)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 61,
   "metadata": {},
   "outputs": [],
   "source": [
    "def filter_edit_distance_tst(fpath, ed=3):\n",
    "    samples_match = 0\n",
    "    with open(fpath) as tst_file:\n",
    "        with open('edited_tst_file.txt', 'w') as efile:\n",
    "            for line in tst_file:\n",
    "                p = line.split('\\t')\n",
    "                edit_distance, _ = find_med_backtrace(p[0], p[1][:-2])\n",
    "                if (edit_distance <= ed):\n",
    "                    samples_match += 1\n",
    "                    efile.write(line)\n",
    "    print(\"Found {} samples that matched to edit distance: {}, file saved as 'edited_tst_file.txt' \".format(samples_match, ed))\n",
    "    "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found 1077 samples that matched to edit distance: 3, file saved as 'edited_tst_file.txt' \n"
     ]
    }
   ],
   "source": [
    "filter_edit_distance_tst(fpath, ed=3)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "True"
      ]
     },
     "execution_count": 53,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "'<PAD>' != '1'"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.5.5"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
